Various merge resolutions from master <- staging

* --ask option ported to new location
* --version option now works
* MSF version updated
* All specs passing
bug/bundler_fix
Samuel Huckins 2014-08-15 11:33:31 -05:00
parent 1183c5cfeb
commit 149c3ecc63
No known key found for this signature in database
GPG Key ID: 5FD48AA28C03C944
261 changed files with 17836 additions and 18770 deletions

3
.gitignore vendored
View File

@ -48,6 +48,9 @@ tags
*.opensdf
*.user
# Rails log directory
/log
# ignore release/debug folders for exploits
external/source/exploits/**/Debug
external/source/exploits/**/Release

View File

@ -53,4 +53,4 @@ Style/StringLiterals:
Style/WordArray:
Enabled: false
Description: 'Metasploit prefers consistent use of []'
Description: 'Metasploit prefers consistent use of []'

View File

@ -3,6 +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

79
Gemfile
View File

@ -1,66 +1,53 @@
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 for Meterpreter on Windows, soon others.
gem 'meterpreter_bins', '0.0.6'
# 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 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'
# Metasploit::Credential database models
gem 'metasploit-credential', '>= 0.8.6', '< 0.9'
# Database models shared between framework and Pro.
gem 'metasploit_data_models', '0.17.0'
gem 'metasploit_data_models', '~> 0.19'
# 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,90 +1,177 @@
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:
activemodel (3.2.14)
activesupport (= 3.2.14)
actionpack (3.2.19)
activemodel (= 3.2.19)
activesupport (= 3.2.19)
builder (~> 3.0.0)
activerecord (3.2.14)
activemodel (= 3.2.14)
activesupport (= 3.2.14)
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)
activerecord (3.2.19)
activemodel (= 3.2.19)
activesupport (= 3.2.19)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activesupport (3.2.14)
activesupport (3.2.19)
i18n (~> 0.6, >= 0.6.4)
multi_json (~> 1.0)
arel (3.0.2)
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)
diff-lcs (1.2.4)
factory_girl (4.2.0)
coderay (1.1.0)
diff-lcs (1.2.5)
erubis (2.7.0)
factory_girl (4.4.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.4.1)
factory_girl (~> 4.4.0)
railties (>= 3.0.0)
fivemat (1.2.1)
i18n (0.6.5)
json (1.8.0)
metasploit_data_models (0.17.0)
activerecord (>= 3.2.13)
hike (1.2.3)
i18n (0.6.11)
journey (1.0.4)
json (1.8.1)
metasploit-concern (0.1.1)
activesupport (~> 3.0, >= 3.0.0)
metasploit-credential (0.8.6)
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
metasploit_data_models (0.19.4)
activerecord (>= 3.2.13, < 4.0.0)
activesupport
arel-helpers
metasploit-concern (~> 0.1.0)
metasploit-model (~> 0.26.1)
pg
meterpreter_bins (0.0.6)
mini_portile (0.5.1)
msgpack (0.5.5)
method_source (0.8.2)
mini_portile (0.6.0)
msgpack (0.5.8)
multi_json (1.0.4)
network_interface (0.0.1)
nokogiri (1.6.0)
mini_portile (~> 0.5.0)
nokogiri (1.6.3.1)
mini_portile (= 0.6.0)
packetfu (1.1.9)
pcaprub (0.11.3)
pg (0.16.0)
rake (10.1.0)
redcarpet (3.0.0)
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.3.2)
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.1.2)
rkelly-remix (0.0.6)
robots (0.10.1)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.5)
rspec-expectations (2.14.2)
rspec (2.99.0)
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rspec-collection_matchers (1.0.0)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (2.99.1)
rspec-expectations (2.99.2)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.3)
shoulda-matchers (2.3.0)
activesupport (>= 3.0.0)
rspec-mocks (2.99.2)
rspec-rails (2.99.0)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-collection_matchers
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rubyntlm (0.4.0)
rubyzip (1.1.6)
shoulda-matchers (2.6.2)
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)
timecop (0.6.3)
tzinfo (0.3.37)
yard (0.8.7)
thor (0.19.1)
tilt (1.4.1)
timecop (0.7.1)
tzinfo (0.3.40)
yard (0.8.7.4)
PLATFORMS
ruby
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)
meterpreter_bins (= 0.0.6)
msgpack
metasploit-credential (>= 0.8.6, < 0.9)
metasploit-framework!
metasploit_data_models (~> 0.19)
network_interface (~> 0.0.1)
nokogiri
packetfu (= 1.1.9)
pcaprub
pg (>= 0.11)
pry
rake (>= 10.0.0)
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

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

File diff suppressed because it is too large Load Diff

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

@ -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`.
#
@ -42,4 +44,4 @@ module Metasploit
@root
end
end
end
end

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,74 @@
#
# 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'
#
# `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

View File

@ -0,0 +1,265 @@
require 'rex/proto/smb'
require 'metasploit/framework'
require 'metasploit/framework/tcp/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 the Server Messaging
# Block protocol.
class SMB
include Metasploit::Framework::Tcp::Client
include Metasploit::Framework::LoginScanner::Base
include Metasploit::Framework::LoginScanner::RexSocket
include Metasploit::Framework::LoginScanner::NTLM
# Constants to be used in {Result#access_level}
module AccessLevels
# Administrative access. For SMB, this is defined as being
# able to successfully Tree Connect to the `ADMIN$` share.
# This definition is not without its problems, but suffices to
# conclude that such a user will most likely be able to use
# psexec.
ADMINISTRATOR = "Administrator"
# Guest access means our creds were accepted but the logon
# session is not associated with a real user account.
GUEST = "Guest"
end
CAN_GET_SESSION = true
DEFAULT_REALM = 'WORKSTATION'
LIKELY_PORTS = [ 139, 445 ]
LIKELY_SERVICE_NAMES = [ "smb" ]
PRIVATE_TYPES = [ :password, :ntlm_hash ]
REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
module StatusCodes
CORRECT_CREDENTIAL_STATUS_CODES = [
"STATUS_ACCOUNT_DISABLED",
"STATUS_ACCOUNT_EXPIRED",
"STATUS_ACCOUNT_RESTRICTION",
"STATUS_INVALID_LOGON_HOURS",
"STATUS_INVALID_WORKSTATION",
"STATUS_LOGON_TYPE_NOT_GRANTED",
"STATUS_PASSWORD_EXPIRED",
"STATUS_PASSWORD_MUST_CHANGE",
].freeze.map(&:freeze)
end
# @!attribute simple
# @return [Rex::Proto::SMB::SimpleClient]
attr_accessor :simple
attr_accessor :smb_chunk_size
attr_accessor :smb_name
attr_accessor :smb_native_lm
attr_accessor :smb_native_os
attr_accessor :smb_obscure_trans_pipe_level
attr_accessor :smb_pad_data_level
attr_accessor :smb_pad_file_level
attr_accessor :smb_pipe_evasion
# UNUSED
#attr_accessor :smb_pipe_read_max_size
#attr_accessor :smb_pipe_read_min_size
#attr_accessor :smb_pipe_write_max_size
#attr_accessor :smb_pipe_write_min_size
attr_accessor :smb_verify_signature
attr_accessor :smb_direct
validates :smb_chunk_size,
numericality:
{
only_integer: true,
greater_than_or_equal_to: 0
}
validates :smb_obscure_trans_pipe_level,
inclusion:
{
in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX
}
validates :smb_pad_data_level,
inclusion:
{
in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX
}
validates :smb_pad_file_level,
inclusion:
{
in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX
}
validates :smb_pipe_evasion,
inclusion: { in: [true, false, nil] },
allow_nil: true
# UNUSED
#validates :smb_pipe_read_max_size, numericality: { only_integer: true }
#validates :smb_pipe_read_min_size, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
#validates :smb_pipe_write_max_size, numericality: { only_integer: true }
#validates :smb_pipe_write_min_size, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :smb_verify_signature,
inclusion: { in: [true, false, nil] },
allow_nil: true
# If login is successul and {Result#access_level} is not set
# then arbitrary credentials are accepted. If it is set to
# Guest, then arbitrary credentials are accepted, but given
# Guest permissions.
#
# @param domain [String] Domain to authenticate against. Use an
# empty string for local accounts.
# @return [Result]
def attempt_bogus_login(domain)
if defined?(@result_for_bogus)
return @result_for_bogus
end
cred = Credential.new(
public: Rex::Text.rand_text_alpha(8),
private: Rex::Text.rand_text_alpha(8),
realm: domain
)
@result_for_bogus = attempt_login(cred)
end
# (see Base#attempt_login)
def attempt_login(credential)
# Disable direct SMB when SMBDirect has not been set and the
# destination port is configured as 139
if self.smb_direct.nil?
self.smb_direct = case self.port
when 139 then false
when 445 then true
end
end
begin
connect
rescue ::Rex::ConnectionError => e
return Result.new(credential:credential, status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
end
proof = nil
begin
# TODO: OMG
simple.login(
smb_name,
credential.public,
credential.private,
credential.realm || "",
smb_verify_signature,
use_ntlmv2,
use_ntlm2_session,
send_lm,
use_lmkey,
send_ntlm,
smb_native_os,
smb_native_lm,
{
use_spn: send_spn,
name: host
}
)
# Windows SMB will return an error code during Session
# Setup, but nix Samba requires a Tree Connect. Try admin$
# first, since that will tell us if this user has local
# admin access. Fall back to IPC$ which should be accessible
# to any user with valid creds.
begin
simple.connect("\\\\#{host}\\admin$")
access_level = AccessLevels::ADMINISTRATOR
simple.disconnect("\\\\#{host}\\admin$")
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
simple.connect("\\\\#{host}\\IPC$")
end
# If we made it this far without raising, we have a valid
# login
status = Metasploit::Model::Login::Status::SUCCESSFUL
rescue ::Rex::Proto::SMB::Exceptions::LoginError => e
status = case e.get_error(e.error_code)
when *StatusCodes::CORRECT_CREDENTIAL_STATUS_CODES
Metasploit::Model::Login::Status::DENIED_ACCESS
when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED'
Metasploit::Model::Login::Status::INCORRECT
else
Metasploit::Model::Login::Status::INCORRECT
end
proof = e
rescue ::Rex::Proto::SMB::Exceptions::Error => e
status = Metasploit::Model::Login::Status::INCORRECT
proof = e
rescue ::Rex::ConnectionError
status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
end
if status == Metasploit::Model::Login::Status::SUCCESSFUL && simple.client.auth_user.nil?
access_level ||= AccessLevels::GUEST
end
Result.new(credential: credential, status: status, proof: proof, access_level: access_level)
end
def connect
disconnect
self.sock = super
c = Rex::Proto::SMB::SimpleClient.new(sock, smb_direct)
c.client.evasion_opts['pad_data'] = smb_pad_data_level
c.client.evasion_opts['pad_file'] = smb_pad_file_level
c.client.evasion_opts['obscure_trans_pipe'] = smb_obscure_trans_pipe_level
self.simple = c
c
end
def set_sane_defaults
self.connection_timeout = 10 if self.connection_timeout.nil?
self.max_send_size = 0 if self.max_send_size.nil?
self.send_delay = 0 if self.send_delay.nil?
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.smb_chunk_size = 0 if self.smb_chunk_size.nil?
self.smb_name = "*SMBSERVER" if self.smb_name.nil?
self.smb_native_lm = "Windows 2000 5.0" if self.smb_native_os.nil?
self.smb_native_os = "Windows 2000 2195" if self.smb_native_os.nil?
self.smb_obscure_trans_pipe_level = 0 if self.smb_obscure_trans_pipe_level.nil?
self.smb_pad_data_level = 0 if self.smb_pad_data_level.nil?
self.smb_pad_file_level = 0 if self.smb_pad_file_level.nil?
self.smb_pipe_evasion = false if self.smb_pipe_evasion.nil?
#self.smb_pipe_read_max_size = 1024 if self.smb_pipe_read_max_size.nil?
#self.smb_pipe_read_min_size = 0 if self.smb_pipe_read_min_size.nil?
#self.smb_pipe_write_max_size = 1024 if self.smb_pipe_write_max_size.nil?
#self.smb_pipe_write_min_size = 0 if self.smb_pipe_write_min_size.nil?
self.smb_verify_signature = false if self.smb_verify_signature.nil?
self.use_lmkey = true 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.smb_name = self.host if self.smb_name.nil?
end
end
end
end
end

View File

@ -0,0 +1,106 @@
require 'snmp'
require 'metasploit/framework/login_scanner/base'
module Metasploit
module Framework
module LoginScanner
# This is the LoginScanner class for dealing with SNMP.
# It is responsible for taking a single target, and a list of credentials
# and attempting them. It then saves the results.
class SNMP
include Metasploit::Framework::LoginScanner::Base
DEFAULT_PORT = 161
LIKELY_PORTS = [ 161, 162 ]
LIKELY_SERVICE_NAMES = [ 'snmp' ]
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 attmpt to login with
# @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object
def attempt_login(credential)
result_options = {
credential: credential
}
[:SNMPv1, :SNMPv2c].each do |version|
snmp_client = ::SNMP::Manager.new(
:Host => host,
:Port => port,
:Community => credential.public,
:Version => version,
:Timeout => connection_timeout,
:Retries => 2,
:Transport => ::SNMP::RexUDPTransport,
:Socket => ::Rex::Socket::Udp.create
)
result_options[:proof] = test_read_access(snmp_client)
if result_options[:proof].nil?
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
else
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
if has_write_access?(snmp_client, result_options[:proof])
result_options[:access_level] = "read-write"
else
result_options[:access_level] = "read-only"
end
end
end
::Metasploit::Framework::LoginScanner::Result.new(result_options)
end
private
# This method takes an snmp client and tests whether
# it has write access to the remote system. It sets the
# the sysDescr oid to the same value we already read.
# @param snmp_client [SNMP::Manager] The SNMP client to use
# @param value [String] the value to set sysDescr back to
# @return [Boolean] Returns true or false for if we have write access
def has_write_access?(snmp_client, value)
var_bind = ::SNMP::VarBind.new("1.3.6.1.2.1.1.1.0", ::SNMP::OctetString.new(value))
begin
resp = snmp_client.set(var_bind)
if resp.error_status == :noError
return true
end
rescue RuntimeError
return false
end
end
# Sets the connection timeout approrpiately for SNMP
# if the user did not set it.
def set_sane_defaults
self.connection_timeout = 2 if self.connection_timeout.nil?
self.port = DEFAULT_PORT if self.port.nil?
end
# This method takes an snmp client and tests whether
# it has read access to the remote system. It checks
# the sysDescr oid to use as proof
# @param snmp_client [SNMP::Manager] The SNMP client to use
# @return [String, nil] Returns a string if successful, nil if failed
def test_read_access(snmp_client)
proof = nil
begin
resp = snmp_client.get("sysDescr.0")
resp.each_varbind { |var| proof = var.value }
rescue RuntimeError
proof = nil
end
proof
end
end
end
end
end

View File

@ -0,0 +1,134 @@
require 'net/ssh'
require 'metasploit/framework/login_scanner/base'
module Metasploit
module Framework
module LoginScanner
# This is the LoginScanner class for dealing with the Secure Shell protocol.
# It is responsible for taking a single target, and a list of credentials
# and attempting them. It then saves the results.
#
class SSH
include Metasploit::Framework::LoginScanner::Base
#
# CONSTANTS
#
CAN_GET_SESSION = true
DEFAULT_PORT = 22
LIKELY_PORTS = [ DEFAULT_PORT ]
LIKELY_SERVICE_NAMES = [ 'ssh' ]
PRIVATE_TYPES = [ :password, :ssh_key ]
REALM_KEY = nil
VERBOSITIES = [
:debug,
:info,
:warn,
:error,
:fatal
]
# @!attribute ssh_socket
# @return [Net::SSH::Connection::Session] The current SSH connection
attr_accessor :ssh_socket
# @!attribute verbosity
# The verbosity level for the SSH client.
#
# @return [Symbol] An element of {VERBOSITIES}.
attr_accessor :verbosity
validates :verbosity,
presence: true,
inclusion: { in: VERBOSITIES }
# (see {Base#attempt_login})
# @note The caller *must* close {#ssh_socket}
def attempt_login(credential)
self.ssh_socket = nil
opt_hash = {
:port => port,
:disable_agent => true,
:config => false,
:verbose => verbosity,
:proxies => proxies
}
case credential.private_type
when :password, nil
opt_hash.update(
:auth_methods => ['password','keyboard-interactive'],
:password => credential.private,
)
when :ssh_key
opt_hash.update(
:auth_methods => ['publickey'],
:key_data => credential.private,
)
end
result_options = {
credential: credential
}
begin
::Timeout.timeout(connection_timeout) do
self.ssh_socket = Net::SSH.start(
host,
credential.public,
opt_hash
)
end
rescue ::EOFError, Net::SSH::Disconnect, Rex::AddressInUse, Rex::ConnectionError, ::Timeout::Error
result_options.merge!( proof: nil, status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
rescue Net::SSH::Exception
result_options.merge!( proof: nil, status: Metasploit::Model::Login::Status::INCORRECT)
end
unless result_options.has_key? :status
if ssh_socket
proof = gather_proof
result_options.merge!( proof: proof, status: Metasploit::Model::Login::Status::SUCCESSFUL)
else
result_options.merge!( proof: nil, status: Metasploit::Model::Login::Status::INCORRECT)
end
end
::Metasploit::Framework::LoginScanner::Result.new(result_options)
end
private
# This method attempts to gather proof that we successfuly logged in.
# @return [String] The proof of a connection, May be empty.
def gather_proof
proof = ''
begin
Timeout.timeout(5) do
proof = ssh_socket.exec!("id\n").to_s
if(proof =~ /id=/)
proof << ssh_socket.exec!("uname -a\n").to_s
else
# Cisco IOS
if proof =~ /Unknown command or computer name/
proof = ssh_socket.exec!("ver\n").to_s
else
proof << ssh_socket.exec!("help\n?\n\n\n").to_s
end
end
end
rescue ::Exception
end
proof
end
def set_sane_defaults
self.connection_timeout = 30 if self.connection_timeout.nil?
self.port = DEFAULT_PORT if self.port.nil?
self.verbosity = :fatal if self.verbosity.nil?
end
end
end
end
end

View File

@ -0,0 +1,113 @@
require 'metasploit/framework/telnet/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 Telnet remote terminals.
# It is responsible for taking a single target, and a list of credentials
# and attempting them. It then saves the results.
class Telnet
include Metasploit::Framework::LoginScanner::Base
include Metasploit::Framework::LoginScanner::RexSocket
include Metasploit::Framework::Telnet::Client
CAN_GET_SESSION = true
DEFAULT_PORT = 23
LIKELY_PORTS = [ DEFAULT_PORT ]
LIKELY_SERVICE_NAMES = [ 'telnet' ]
PRIVATE_TYPES = [ :password ]
REALM_KEY = nil
# @!attribute verbosity
# The timeout to wait for the telnet banner.
#
# @return [Fixnum]
attr_accessor :banner_timeout
# @!attribute verbosity
# The timeout to wait for the response from a telnet command.
#
# @return [Fixnum]
attr_accessor :telnet_timeout
validates :banner_timeout,
presence: true,
numericality: {
only_integer: true,
greater_than_or_equal_to: 1
}
validates :telnet_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
}
if connect_reset_safe == :refused
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
else
if busy_message?
self.sock.close unless self.sock.closed?
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
end
end
unless result_options[:status]
unless password_prompt?
send_user(credential.public)
end
recvd_sample = @recvd.dup
# Allow for slow echos
1.upto(10) do
recv_telnet(self.sock, 0.10) unless @recvd.nil? or @recvd[/#{@password_prompt}/]
end
if password_prompt?(credential.public)
send_pass(credential.private)
# Allow for slow echos
1.upto(10) do
recv_telnet(self.sock, 0.10) if @recvd == recvd_sample
end
end
if login_succeeded?
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
else
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
end
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.max_send_size ||= 0
self.port ||= DEFAULT_PORT
self.send_delay ||= 0
self.banner_timeout ||= 25
self.telnet_timeout ||= 10
self.connection_timeout ||= 30
# Shim to set up the ivars from the old Login mixin
create_login_ivars
end
end
end
end
end

View File

@ -0,0 +1,28 @@
require 'metasploit/framework/login_scanner/http'
module Metasploit
module Framework
module LoginScanner
# Tomcat Manager login scanner
class Tomcat < HTTP
# Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP
CAN_GET_SESSION = true
DEFAULT_PORT = 8180
PRIVATE_TYPES = [ :password ]
# (see Base#set_sane_defaults)
def set_sane_defaults
self.uri = "/manager/html" if self.uri.nil?
self.method = "GET" if self.method.nil?
super
end
end
end
end
end

View File

@ -0,0 +1,122 @@
require 'metasploit/framework/tcp/client'
require 'rex/proto/rfb'
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 the VNC RFB protocol.
# It is responsible for taking a single target, and a list of credentials
# and attempting them. It then saves the results.
class VNC
include Metasploit::Framework::LoginScanner::Base
include Metasploit::Framework::LoginScanner::RexSocket
include Metasploit::Framework::Tcp::Client
#
# CONSTANTS
#
LIKELY_PORTS = (5900..5910).to_a
LIKELY_SERVICE_NAMES = [ 'vnc' ]
PRIVATE_TYPES = [ :password ]
REALM_KEY = nil
# Error indicating retry should occur for UltraVNC
ULTRA_VNC_RETRY_ERROR = 'connection has been rejected'
# Error indicating retry should occur for VNC 4 Server
VNC4_SERVER_RETRY_ERROR = 'Too many security failures'
# Known retry errors for all supported versions of VNC
RETRY_ERRORS = [
ULTRA_VNC_RETRY_ERROR,
VNC4_SERVER_RETRY_ERROR
]
# 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
}
begin
# Make our initial socket to the target
disconnect if self.sock
connect
# Create our VNC client overtop of the socket
vnc = Rex::Proto::RFB::Client.new(sock, :allow_none => false)
if vnc.handshake
if vnc_auth(vnc,credential.private)
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
else
result_options.merge!(
proof: vnc.error,
status: Metasploit::Model::Login::Status::INCORRECT
)
end
else
result_options.merge!(
proof: vnc.error,
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
)
end
rescue ::EOFError, Errno::ENOTCONN, Rex::AddressInUse, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error => e
result_options.merge!(
proof: e.message,
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
)
end
::Metasploit::Framework::LoginScanner::Result.new(result_options)
end
private
# Check the VNC error to see if we should wait and retry.
#
# @param error [String] The VNC error message received
# @return [false] don't retry
# @return [true] retry
def retry?(error)
RETRY_ERRORS.include?(error)
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 ||= 5900
self.max_send_size ||= 0
self.send_delay ||= 0
end
# This method attempts the actual VNC authentication. It has built in retries to handle
# delays built into the VNC RFB authentication.
# @param client [Rex::Proto::RFB::Client] The VNC client object to authenticate through
# @param password [String] the password to attempt the authentication with
def vnc_auth(client,password)
success = false
5.times do |n|
if client.authenticate(password)
success = true
break
end
break unless retry?(client.error)
# Wait for an increasing ammount of time before retrying
delay = (2**(n+1)) + 1
::Rex.sleep(delay)
end
success
end
end
end
end
end

View File

@ -0,0 +1,52 @@
require 'metasploit/framework/login_scanner/base'
require 'metasploit/framework/login_scanner/rex_socket'
require 'metasploit/framework/login_scanner/http'
module Metasploit
module Framework
module LoginScanner
# Windows Remote Management login scanner
class WinRM < HTTP
# The default port where WinRM listens. This is what you get on
# v1.1+ with `winrm quickconfig`. Note that before v1.1, the
# default was 80
DEFAULT_PORT = 5985
# The default realm is WORKSTATION which tells Windows authentication
# that it is a Local Account.
DEFAULT_REALM = 'WORKSTATION'
# The default port where WinRM listens when SSL is enabled. Note
# that before v1.1, the default was 443
DEFAULT_SSL_PORT = 5986
PRIVATE_TYPES = [ :password ]
LIKELY_PORTS = [ 80, 443, 5985, 5986 ]
REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
# Inherit LIKELY_SERVICE_NAMES, since a scanner will see it as
# just HTTP.
validates :method, inclusion: { in: ["POST"] }
# (see Base#set_sane_defaults)
def set_sane_defaults
self.uri = "/wsman" if self.uri.nil?
@method = "POST".freeze
super
end
# The method *must* be "POST", so don't let the user change it
# @raise [RuntimeError] Unconditionally
def method=(_)
raise RuntimeError, "Method must be POST for WinRM"
end
end
end
end
end

View File

@ -0,0 +1,728 @@
require 'metasploit/framework/tcp/client'
module Metasploit
module Framework
module MSSQL
module Client
include Metasploit::Framework::Tcp::Client
NTLM_CRYPT = Rex::Proto::NTLM::Crypt
NTLM_CONST = Rex::Proto::NTLM::Constants
NTLM_UTILS = Rex::Proto::NTLM::Utils
NTLM_XCEPT = Rex::Proto::NTLM::Exceptions
# Encryption
ENCRYPT_OFF = 0x00 #Encryption is available but off.
ENCRYPT_ON = 0x01 #Encryption is available and on.
ENCRYPT_NOT_SUP = 0x02 #Encryption is not available.
ENCRYPT_REQ = 0x03 #Encryption is required.
# Packet Type
TYPE_SQL_BATCH = 1 # (Client) SQL command
TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused)
TYPE_RPC = 3 # (Client) RPC
TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters,
# Request Completion, Error and Info Messages, Attention Acknowledgement
TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention
TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data
TYPE_TRANSACTION_MANAGER_REQUEST = 14 # (Client) Transaction request manager
TYPE_TDS7_LOGIN = 16 # (Client) Login
TYPE_SSPI_MESSAGE = 17 # (Client) Login
TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 7
# Status
STATUS_NORMAL = 0x00
STATUS_END_OF_MESSAGE = 0x01
STATUS_IGNORE_EVENT = 0x02
STATUS_RESETCONNECTION = 0x08 # TDS 7.1+
STATUS_RESETCONNECTIONSKIPTRAN = 0x10 # TDS 7.3+
#
# This method connects to the server over TCP and attempts
# to authenticate with the supplied username and password
# The global socket is used and left connected after auth
#
def mssql_login(user='sa', pass='', db='', domain_name='')
disconnect if self.sock
connect
# Send a prelogin packet and check that encryption is not enabled
if mssql_prelogin() != ENCRYPT_NOT_SUP
print_error("Encryption is not supported")
return false
end
if windows_authentication
idx = 0
pkt = ''
pkt_hdr = ''
pkt_hdr = [
TYPE_TDS7_LOGIN, #type
STATUS_END_OF_MESSAGE, #status
0x0000, #length
0x0000, # SPID
0x01, # PacketID (unused upon specification
# but ms network monitor stil prefer 1 to decode correctly, wireshark don't care)
0x00 #Window
]
pkt << [
0x00000000, # Size
0x71000001, # TDS Version
0x00000000, # Dummy Size
0x00000007, # Version
rand(1024+1), # PID
0x00000000, # ConnectionID
0xe0, # Option Flags 1
0x83, # Option Flags 2
0x00, # SQL Type Flags
0x00, # Reserved Flags
0x00000000, # Time Zone
0x00000000 # Collation
].pack('VVVVVVCCCCVV')
cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) #application and library name
sname = Rex::Text.to_unicode( rhost )
dname = Rex::Text.to_unicode( db )
ntlm_options = {
:signing => false,
:usentlm2_session => use_ntlm2_session,
:use_ntlmv2 => use_ntlmv2,
:send_lm => send_lm,
:send_ntlm => send_ntlm
}
ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options)
workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)
ntlmsspblob = NTLM_UTILS::make_ntlmssp_blob_init(domain_name, workstation_name, ntlmssp_flags)
idx = pkt.size + 50 # lengths below
pkt << [idx, cname.length / 2].pack('vv')
idx += cname.length
pkt << [0, 0].pack('vv') # User length offset must be 0
pkt << [0, 0].pack('vv') # Password length offset must be 0
pkt << [idx, aname.length / 2].pack('vv')
idx += aname.length
pkt << [idx, sname.length / 2].pack('vv')
idx += sname.length
pkt << [0, 0].pack('vv') # unused
pkt << [idx, aname.length / 2].pack('vv')
idx += aname.length
pkt << [idx, 0].pack('vv') # locales
pkt << [idx, 0].pack('vv') #db
# ClientID (should be mac address)
pkt << Rex::Text.rand_text(6)
# NTLMSSP
pkt << [idx, ntlmsspblob.length].pack('vv')
idx += ntlmsspblob.length
pkt << [idx, 0].pack('vv') # AtchDBFile
pkt << cname
pkt << aname
pkt << sname
pkt << aname
pkt << ntlmsspblob
# Total packet length
pkt[0,4] = [pkt.length].pack('V')
pkt_hdr[2] = pkt.length + 8
pkt = pkt_hdr.pack("CCnnCC") + pkt
# Rem : One have to set check_status to false here because sql server sp0 (and maybe above)
# has a strange behavior that differs from the specifications
# upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header
# is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification
resp = mssql_send_recv(pkt,15, false)
# Get default data
begin
blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(resp)
# a domain.length < 3 will hit this
rescue NTLM_XCEPT::NTLMMissingChallenge
return false
end
challenge_key = blob_data[:challenge_key]
server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error
#netbios name
default_name = blob_data[:default_name] || ''
#netbios domain
default_domain = blob_data[:default_domain] || ''
#dns name
dns_host_name = blob_data[:dns_host_name] || ''
#dns domain
dns_domain_name = blob_data[:dns_domain_name] || ''
#Client time
chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || ''
spnopt = {:use_spn => send_spn, :name => self.rhost}
resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(user, pass, challenge_key,
domain_name, default_name, default_domain,
dns_host_name, dns_domain_name, chall_MsvAvTimestamp,
spnopt, ntlm_options)
ntlmssp = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, user, resp_lm, resp_ntlm, '', ntlmssp_flags)
# Create an SSPIMessage
idx = 0
pkt = ''
pkt_hdr = ''
pkt_hdr = [
TYPE_SSPI_MESSAGE, #type
STATUS_END_OF_MESSAGE, #status
0x0000, #length
0x0000, # SPID
0x01, # PacketID
0x00 #Window
]
pkt_hdr[2] = ntlmssp.length + 8
pkt = pkt_hdr.pack("CCnnCC") + ntlmssp
resp = mssql_send_recv(pkt)
#SQL Server Authentification
else
idx = 0
pkt = ''
pkt << [
0x00000000, # Dummy size
0x71000001, # TDS Version
0x00000000, # Size
0x00000007, # Version
rand(1024+1), # PID
0x00000000, # ConnectionID
0xe0, # Option Flags 1
0x03, # Option Flags 2
0x00, # SQL Type Flags
0x00, # Reserved Flags
0x00000000, # Time Zone
0x00000000 # Collation
].pack('VVVVVVCCCCVV')
cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
uname = Rex::Text.to_unicode( user )
pname = mssql_tds_encrypt( pass )
aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
sname = Rex::Text.to_unicode( rhost )
dname = Rex::Text.to_unicode( db )
idx = pkt.size + 50 # lengths below
pkt << [idx, cname.length / 2].pack('vv')
idx += cname.length
pkt << [idx, uname.length / 2].pack('vv')
idx += uname.length
pkt << [idx, pname.length / 2].pack('vv')
idx += pname.length
pkt << [idx, aname.length / 2].pack('vv')
idx += aname.length
pkt << [idx, sname.length / 2].pack('vv')
idx += sname.length
pkt << [0, 0].pack('vv')
pkt << [idx, aname.length / 2].pack('vv')
idx += aname.length
pkt << [idx, 0].pack('vv')
pkt << [idx, dname.length / 2].pack('vv')
idx += dname.length
# The total length has to be embedded twice more here
pkt << [
0,
0,
0x12345678,
0x12345678
].pack('vVVV')
pkt << cname
pkt << uname
pkt << pname
pkt << aname
pkt << sname
pkt << aname
pkt << dname
# Total packet length
pkt[0,4] = [pkt.length].pack('V')
# Embedded packet lengths
pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2
# Packet header and total length including header
pkt = "\x10\x01" + [pkt.length + 8].pack('n') + [0].pack('n') + [1].pack('C') + "\x00" + pkt
resp = mssql_send_recv(pkt)
end
info = {:errors => []}
info = mssql_parse_reply(resp,info)
return false if not info
info[:login_ack] ? true : false
end
#
# Parse an "environment change" TDS token
#
def mssql_parse_env(data, info)
len = data.slice!(0,2).unpack('v')[0]
buff = data.slice!(0,len)
type = buff.slice!(0,1).unpack('C')[0]
nval = ''
nlen = buff.slice!(0,1).unpack('C')[0] || 0
nval = buff.slice!(0,nlen*2).gsub("\x00", '') if nlen > 0
oval = ''
olen = buff.slice!(0,1).unpack('C')[0] || 0
oval = buff.slice!(0,olen*2).gsub("\x00", '') if olen > 0
info[:envs] ||= []
info[:envs] << { :type => type, :old => oval, :new => nval }
info
end
#
# Parse a "ret" TDS token
#
def mssql_parse_ret(data, info)
ret = data.slice!(0,4).unpack('N')[0]
info[:ret] = ret
info
end
#
# Parse a "done" TDS token
#
def mssql_parse_done(data, info)
status,cmd,rows = data.slice!(0,8).unpack('vvV')
info[:done] = { :status => status, :cmd => cmd, :rows => rows }
info
end
#
# Parse an "error" TDS token
#
def mssql_parse_error(data, info)
len = data.slice!(0,2).unpack('v')[0]
buff = data.slice!(0,len)
errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv')
emsg = buff.slice!(0,elen * 2)
emsg.gsub!("\x00", '')
info[:errors] << "SQL Server Error ##{errno} (State:#{state} Severity:#{sev}): #{emsg}"
info
end
#
# Parse an "information" TDS token
#
def mssql_parse_info(data, info)
len = data.slice!(0,2).unpack('v')[0]
buff = data.slice!(0,len)
errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv')
emsg = buff.slice!(0,elen * 2)
emsg.gsub!("\x00", '')
info[:infos]||= []
info[:infos] << "SQL Server Info ##{errno} (State:#{state} Severity:#{sev}): #{emsg}"
info
end
#
# Parse a "login ack" TDS token
#
def mssql_parse_login_ack(data, info)
len = data.slice!(0,2).unpack('v')[0]
buff = data.slice!(0,len)
info[:login_ack] = true
end
#
# Parse individual tokens from a TDS reply
#
def mssql_parse_reply(data, info)
info[:errors] = []
return if not data
until data.empty?
token = data.slice!(0,1).unpack('C')[0]
case token
when 0x81
mssql_parse_tds_reply(data, info)
when 0xd1
mssql_parse_tds_row(data, info)
when 0xe3
mssql_parse_env(data, info)
when 0x79
mssql_parse_ret(data, info)
when 0xfd, 0xfe, 0xff
mssql_parse_done(data, info)
when 0xad
mssql_parse_login_ack(data, info)
when 0xab
mssql_parse_info(data, info)
when 0xaa
mssql_parse_error(data, info)
when nil
break
else
info[:errors] << "unsupported token: #{token}"
end
end
info
end
#
# Parse a raw TDS reply from the server
#
def mssql_parse_tds_reply(data, info)
info[:errors] ||= []
info[:colinfos] ||= []
info[:colnames] ||= []
# Parse out the columns
cols = data.slice!(0,2).unpack('v')[0]
0.upto(cols-1) do |col_idx|
col = {}
info[:colinfos][col_idx] = col
col[:utype] = data.slice!(0,2).unpack('v')[0]
col[:flags] = data.slice!(0,2).unpack('v')[0]
col[:type] = data.slice!(0,1).unpack('C')[0]
case col[:type]
when 48
col[:id] = :tinyint
when 52
col[:id] = :smallint
when 56
col[:id] = :rawint
when 61
col[:id] = :datetime
when 34
col[:id] = :image
col[:max_size] = data.slice!(0,4).unpack('V')[0]
col[:value_length] = data.slice!(0,2).unpack('v')[0]
col[:value] = data.slice!(0, col[:value_length] * 2).gsub("\x00", '')
when 36
col[:id] = :string
when 38
col[:id] = :int
col[:int_size] = data.slice!(0,1).unpack('C')[0]
when 127
col[:id] = :bigint
when 165
col[:id] = :hex
col[:max_size] = data.slice!(0,2).unpack('v')[0]
when 173
col[:id] = :hex # binary(2)
col[:max_size] = data.slice!(0,2).unpack('v')[0]
when 231,175,167,239
col[:id] = :string
col[:max_size] = data.slice!(0,2).unpack('v')[0]
col[:codepage] = data.slice!(0,2).unpack('v')[0]
col[:cflags] = data.slice!(0,2).unpack('v')[0]
col[:charset_id] = data.slice!(0,1).unpack('C')[0]
else
col[:id] = :unknown
end
col[:msg_len] = data.slice!(0,1).unpack('C')[0]
if(col[:msg_len] and col[:msg_len] > 0)
col[:name] = data.slice!(0, col[:msg_len] * 2).gsub("\x00", '')
end
info[:colnames] << (col[:name] || 'NULL')
end
end
#
# Parse a single row of a TDS reply
#
def mssql_parse_tds_row(data, info)
info[:rows] ||= []
row = []
info[:colinfos].each do |col|
if(data.length == 0)
row << "<EMPTY>"
next
end
case col[:id]
when :hex
str = ""
len = data.slice!(0,2).unpack('v')[0]
if(len > 0 and len < 65535)
str << data.slice!(0,len)
end
row << str.unpack("H*")[0]
when :string
str = ""
len = data.slice!(0,2).unpack('v')[0]
if(len > 0 and len < 65535)
str << data.slice!(0,len)
end
row << str.gsub("\x00", '')
when :datetime
row << data.slice!(0,8).unpack("H*")[0]
when :rawint
row << data.slice!(0,4).unpack('V')[0]
when :bigint
row << data.slice!(0,8).unpack("H*")[0]
when :smallint
row << data.slice!(0, 2).unpack("v")[0]
when :smallint3
row << [data.slice!(0, 3)].pack("Z4").unpack("V")[0]
when :tinyint
row << data.slice!(0, 1).unpack("C")[0]
when :image
str = ''
len = data.slice!(0,1).unpack('C')[0]
str = data.slice!(0,len) if (len and len > 0)
row << str.unpack("H*")[0]
when :int
len = data.slice!(0, 1).unpack("C")[0]
raw = data.slice!(0, len) if (len and len > 0)
case len
when 0,255
row << ''
when 1
row << raw.unpack("C")[0]
when 2
row << raw.unpack('v')[0]
when 4
row << raw.unpack('V')[0]
when 5
row << raw.unpack('V')[0] # XXX: missing high byte
when 8
row << raw.unpack('VV')[0] # XXX: missing high dword
else
info[:errors] << "invalid integer size: #{len} #{data[0,16].unpack("H*")[0]}"
end
else
info[:errors] << "unknown column type: #{col.inspect}"
end
end
info[:rows] << row
info
end
#
#this method send a prelogin packet and check if encryption is off
#
def mssql_prelogin(enc_error=false)
pkt = ""
pkt_hdr = ""
pkt_data_token = ""
pkt_data = ""
pkt_hdr = [
TYPE_PRE_LOGIN_MESSAGE, #type
STATUS_END_OF_MESSAGE, #status
0x0000, #length
0x0000, # SPID
0x00, # PacketID
0x00 #Window
]
version = [0x55010008,0x0000].pack("Vv")
encryption = ENCRYPT_NOT_SUP # off
instoptdata = "MSSQLServer\0"
threadid = "\0\0" + Rex::Text.rand_text(2)
idx = 21 # size of pkt_data_token
pkt_data_token << [
0x00, # Token 0 type Version
idx , # VersionOffset
version.length, # VersionLength
0x01, # Token 1 type Encryption
idx = idx + version.length, # EncryptionOffset
0x01, # EncryptionLength
0x02, # Token 2 type InstOpt
idx = idx + 1, # InstOptOffset
instoptdata.length, # InstOptLength
0x03, # Token 3 type Threadid
idx + instoptdata.length, # ThreadIdOffset
0x04, # ThreadIdLength
0xFF
].pack("CnnCnnCnnCnnC")
pkt_data << pkt_data_token
pkt_data << version
pkt_data << encryption
pkt_data << instoptdata
pkt_data << threadid
pkt_hdr[2] = pkt_data.length + 8
pkt = pkt_hdr.pack("CCnnCC") + pkt_data
resp = mssql_send_recv(pkt)
idx = 0
while resp and resp[0,1] != "\xff" and resp.length > 5
token = resp.slice!(0,5)
token = token.unpack("Cnn")
idx -= 5
if token[0] == 0x01
idx += token[1]
break
end
end
if idx > 0
encryption_mode = resp[idx,1].unpack("C")[0]
else
#force to ENCRYPT_NOT_SUP and hope for the best
encryption_mode = ENCRYPT_NOT_SUP
end
if encryption_mode != ENCRYPT_NOT_SUP and enc_error
raise RuntimeError,"Encryption is not supported"
end
encryption_mode
end
#
# Send and receive using TDS
#
def mssql_send_recv(req, timeout=15, check_status = true)
sock.put(req)
# Read the 8 byte header to get the length and status
# Read the length to get the data
# If the status is 0, read another header and more data
done = false
resp = ""
while(not done)
head = sock.get_once(8, timeout)
if !(head and head.length == 8)
return false
end
# Is this the last buffer?
if(head[1,1] == "\x01" or not check_status )
done = true
end
# Grab this block's length
rlen = head[2,2].unpack('n')[0] - 8
while(rlen > 0)
buff = sock.get_once(rlen, timeout)
return if not buff
resp << buff
rlen -= buff.length
end
end
resp
end
#
# Encrypt a password according to the TDS protocol (encode)
#
def mssql_tds_encrypt(pass)
# Convert to unicode, swap 4 bits both ways, xor with 0xa5
Rex::Text.to_unicode(pass).unpack('C*').map {|c| (((c & 0x0f) << 4) + ((c & 0xf0) >> 4)) ^ 0xa5 }.pack("C*")
end
protected
def windows_authentication
raise NotImplementedError
end
def use_ntlm2_session
raise NotImplementedError
end
def use_ntlmv2
raise NotImplementedError
end
def send_lm
raise NotImplementedError
end
def send_ntlm
raise NotImplementedError
end
def send_spn
raise NotImplementedError
end
end
end
end
end

View File

@ -0,0 +1,27 @@
#
# Gems
#
require 'active_support/dependencies/autoload'
# @note Must use the nested declaration of the
# {Metasploit::Framework::ParsedOptions} namespace because commands, which
# use parsed options, 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
# Namespace for parsed options for {Metasploit::Framework::Command
# commands}. The names of `Class`es in this namespace correspond to the
# name of the `Class` in the {Metasploit::Framework::Command} namespace
# for which this namespace's `Class` parses options.
module ParsedOptions
extend ActiveSupport::Autoload
autoload :Base
autoload :Console
end
end
end

View File

@ -0,0 +1,185 @@
#
# Standard Library
#
require 'optparse'
#
# Gems
#
require 'active_support/ordered_options'
#
# Project
#
require 'metasploit/framework/parsed_options'
require 'msf/base/config'
# Options parsed from the command line that can be used to change the
# `Metasploit::Framework::Application.config` and `Rails.env`
class Metasploit::Framework::ParsedOptions::Base
#
# CONSTANTS
#
# msfconsole boots in production mode instead of the normal rails default of
# development.
DEFAULT_ENVIRONMENT = 'production'
#
# Attributes
#
attr_reader :positional
#
# Instance Methods
#
def initialize(arguments=ARGV)
@positional = option_parser.parse(arguments)
end
# Translates {#options} to the `application`'s config
#
# @param application [Rails::Application]
# @return [void]
def configure(application)
application.config['config/database'] = options.database.config
end
# Sets the `RAILS_ENV` environment variable.
#
# 1. If the -E/--environment option is given, then its value is used.
# 2. The default value, 'production', is used.
#
# @return [void]
def environment!
if defined?(Rails) && Rails.instance_variable_defined?(:@_env) && Rails.env != options.environment
raise "#{self.class}##{__method__} called too late to set RAILS_ENV: Rails.env already memoized"
end
ENV['RAILS_ENV'] = options.environment
end
# Options parsed from
#
# @return [ActiveSupport::OrderedOptions]
def options
unless @options
options = ActiveSupport::OrderedOptions.new
options.database = ActiveSupport::OrderedOptions.new
user_config_root = Pathname.new(Msf::Config.get_config_root)
user_database_yaml = user_config_root.join('database.yml')
if user_database_yaml.exist?
options.database.config = user_database_yaml.to_path
else
options.database.config = 'config/database.yml'
end
options.database.disable = false
options.database.migrations_paths = []
# If RAILS_ENV is set, then it will be used, but if RAILS_ENV is set and the --environment option is given, then
# --environment value will be used to reset ENV[RAILS_ENV].
options.environment = ENV['RAILS_ENV'] || DEFAULT_ENVIRONMENT
options.framework = ActiveSupport::OrderedOptions.new
options.framework.config = nil
options.modules = ActiveSupport::OrderedOptions.new
options.modules.path = nil
@options = options
end
@options
end
private
# Parses arguments into {#options}.
#
# @return [OptionParser]
def option_parser
@option_parser ||= OptionParser.new { |option_parser|
option_parser.separator ''
option_parser.separator 'Common options'
option_parser.on(
'-E',
'--environment ENVIRONMENT',
%w{development production test},
"The Rails environment. Will use RAIL_ENV environment variable if that is set. " \
"Defaults to production if neither option not RAILS_ENV environment variable is set."
) do |environment|
options.environment = environment
end
option_parser.separator ''
option_parser.separator 'Database options'
option_parser.on(
'-M',
'--migration-path DIRECTORY',
'Specify a directory containing additional DB migrations'
) do |directory|
options.database.migrations_paths << directory
end
option_parser.on('-n', '--no-database', 'Disable database support') do
options.database.disable = true
end
option_parser.on(
'-y',
'--yaml PATH',
'Specify a YAML file containing database settings'
) do |path|
options.database.config = path
end
option_parser.separator ''
option_parser.separator 'Framework options'
option_parser.on('-c', '-c FILE', 'Load the specified configuration file') do |file|
options.framework.config = file
end
option_parser.on(
'-v',
'--version',
'Show version'
) do
options.subcommand = :version
end
option_parser.separator ''
option_parser.separator 'Module options'
option_parser.on(
'-m',
'--module-path DIRECTORY',
'An additional module path'
) do |directory|
options.modules.path = directory
end
#
# Tail
#
option_parser.separator ''
option_parser.on_tail('-h', '--help', 'Show this message') do
puts option_parser
exit
end
}
end
end

View File

@ -0,0 +1,79 @@
# Parsed options for {Metasploit::Framework::Command::Console}
class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::ParsedOptions::Base
# Options parsed from msfconsole command-line.
#
# @return [ActiveSupport::OrderedOptions]
def options
unless @options
super.tap { |options|
options.console = ActiveSupport::OrderedOptions.new
options.console.commands = []
options.console.confirm_exit = false
options.console.defanged = false
options.console.local_output = nil
options.console.plugins = []
options.console.quiet = false
options.console.real_readline = false
options.console.resources = []
options.console.subcommand = :run
}
end
@options
end
private
# Parses msfconsole arguments into {#options}.
#
# @return [OptionParser]
def option_parser
unless @option_parser
super.tap { |option_parser|
option_parser.banner = "Usage: #{option_parser.program_name} [options]"
option_parser.separator ''
option_parser.separator 'Console options:'
option_parser.on('-a', '--ask', "Ask before exiting Metasploit or accept 'exit -y'") do
options.console.confirm_exit = true
end
option_parser.on('-d', '--defanged', 'Execute the console as defanged') do
options.console.defanged = true
end
option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do
options.console.real_readline = true
end
option_parser.on('-o', '--output FILE', 'Output to the specified file') do |file|
options.console.local_output = file
end
option_parser.on('-p', '--plugin PLUGIN', 'Load a plugin on startup') do |plugin|
options.console.plugins << plugin
end
option_parser.on('-q', '--quiet', 'Do not print the banner on start up') do
options.console.quiet = true
end
option_parser.on('-r', '--resource FILE', 'Execute the specified resource file') do |file|
options.console.resources << file
end
option_parser.on(
'-x',
'--execute-command COMMAND',
'Execute the specified string as console commands (use ; for multiples)'
) do |commands|
options.console.commands += commands.split(/\s*;\s*/)
end
}
end
@option_parser
end
end

View File

@ -0,0 +1,92 @@
# @note needs to use explicit nesting. so this file can be loaded directly without loading 'metasploit/framework', this
# file can be used prior to Bundler.require.
module Metasploit
module Framework
# Extension to `Kernel#require` behavior.
module Require
#
# Module Methods
#
# Tries to require `name`. If a `LoadError` occurs, then `without_warning` is printed to standard error using
# `Kernel#warn`, along with instructions for reinstalling the bundle. If a `LoadError` does not occur, then
# `with_block` is called.
#
# @param name [String] the name of the library to `Kernel#require`.
# @param without_warning [String] warning to print if `name` cannot be required.
# @yield block to run when `name` requires successfully
# @yieldreturn [void]
# @return [void]
def self.optionally(name, without_warning)
begin
require name
rescue LoadError
warn without_warning
warn "Bundle installed '--without #{Bundler.settings.without.join(' ')}'"
warn "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`"
else
if block_given?
yield
end
end
end
# Tries to `require 'active_record/railtie'` to define the activerecord Rails initializers and rake tasks.
#
# @example Optionally requiring 'active_record/railtie'
# require 'metasploit/framework/require'
#
# class MyClass
# def setup
# if database_enabled
# Metasploit::Framework::Require.optionally_active_record_railtie
# end
# end
# end
#
# @return [void]
def self.optionally_active_record_railtie
optionally(
'active_record/railtie',
'activerecord not in the bundle, so database support will be disabled.'
)
end
# Tries to `require 'metasploit/credential/creation'` and include it in the `including_module`.
#
# @param including_module [Module] `Class` or `Module` that wants to `include Metasploit::Credential::Creation`.
# @return [void]
def self.optionally_include_metasploit_credential_creation(including_module)
optionally(
'metasploit/credential/creation',
"metasploit-credential not in the bundle, so Metasploit::Credential creation will fail for #{including_module.name}",
) do
including_module.send(:include, Metasploit::Credential::Creation)
end
end
#
# Instance Methods
#
# Tries to `require 'metasploit/credential/creation'` and include it in this `Class` or `Module`.
#
# @example Using in a `Module`
# require 'metasploit/framework/require'
#
# module MyModule
# extend Metasploit::Framework::Require
#
# optionally_include_metasploit_credential_creation
# end
#
# @return [void]
def optionally_include_metasploit_credential_creation
Metasploit::Framework::Require.optionally_include_metasploit_credential_creation(self)
end
end
end
end

View File

@ -0,0 +1,186 @@
module Metasploit
module Framework
module Tcp
module EvasiveTCP
attr_accessor :_send_size, :_send_delay, :evasive
def denagle
begin
setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
rescue ::Exception
end
end
def write(buf, opts={})
return super(buf, opts) if not @evasive
ret = 0
idx = 0
len = @_send_size || buf.length
while(idx < buf.length)
if(@_send_delay and idx > 0)
::IO.select(nil, nil, nil, @_send_delay)
end
pkt = buf[idx, len]
res = super(pkt, opts)
flush()
idx += len
ret += res if res
end
ret
end
end
module Client
#
# Establishes a TCP connection to the specified RHOST/RPORT
#
# @see Rex::Socket::Tcp
# @see Rex::Socket::Tcp.create
def connect(global = true, opts={})
dossl = false
if(opts.has_key?('SSL'))
dossl = opts['SSL']
else
dossl = ssl
end
nsock = Rex::Socket::Tcp.create(
'PeerHost' => opts['RHOST'] || rhost,
'PeerPort' => (opts['RPORT'] || rport).to_i,
'LocalHost' => opts['CHOST'] || chost || "0.0.0.0",
'LocalPort' => (opts['CPORT'] || cport || 0).to_i,
'SSL' => dossl,
'SSLVersion' => opts['SSLVersion'] || ssl_version,
'Proxies' => proxies,
'Timeout' => (opts['ConnectTimeout'] || connection_timeout || 10).to_i
)
# enable evasions on this socket
set_tcp_evasions(nsock)
# Set this socket to the global socket as necessary
self.sock = nsock if (global)
return nsock
end
# Enable evasions on a given client
def set_tcp_evasions(socket)
if( max_send_size.to_i == 0 and send_delay.to_i == 0)
return
end
return if socket.respond_to?('evasive')
socket.extend(EvasiveTCP)
if ( max_send_size.to_i > 0)
socket._send_size = max_send_size
socket.denagle
socket.evasive = true
end
if ( send_delay.to_i > 0)
socket._send_delay = send_delay
socket.evasive = true
end
end
#
# Closes the TCP connection
#
def disconnect(nsock = self.sock)
begin
if (nsock)
nsock.shutdown
nsock.close
end
rescue IOError
end
if (nsock == sock)
self.sock = nil
end
end
##
#
# Wrappers for getters
#
##
def max_send_size
raise NotImplementedError
end
def send_delay
raise NotImplementedError
end
#
# Returns the target host
#
def rhost
raise NotImplementedError
end
#
# Returns the remote port
#
def rport
raise NotImplementedError
end
#
# Returns the local host for outgoing connections
#
def chost
raise NotImplementedError
end
#
# Returns the local port for outgoing connections
#
def cport
raise NotImplementedError
end
#
# Returns the boolean indicating SSL
#
def ssl
raise NotImplementedError
end
#
# Returns the string indicating SSLVersion
#
def ssl_version
raise NotImplementedError
end
#
# Returns the proxy configuration
#
def proxies
raise NotImplementedError
end
attr_accessor :sock
end
end
end
end

View File

@ -0,0 +1,219 @@
require 'metasploit/framework/tcp/client'
module Metasploit
module Framework
module Telnet
module Client
include Metasploit::Framework::Tcp::Client
include Msf::Auxiliary::Login
attr_accessor :banner
#
# CONSTANTS
#
# Borrowing constants from Ruby's Net::Telnet class (ruby license)
IAC = 255.chr # "\377" # "\xff" # interpret as command
DONT = 254.chr # "\376" # "\xfe" # you are not to use option
DO = 253.chr # "\375" # "\xfd" # please, you use option
WONT = 252.chr # "\374" # "\xfc" # I won't use option
WILL = 251.chr # "\373" # "\xfb" # I will use option
SB = 250.chr # "\372" # "\xfa" # interpret as subnegotiation
GA = 249.chr # "\371" # "\xf9" # you may reverse the line
EL = 248.chr # "\370" # "\xf8" # erase the current line
EC = 247.chr # "\367" # "\xf7" # erase the current character
AYT = 246.chr # "\366" # "\xf6" # are you there
AO = 245.chr # "\365" # "\xf5" # abort output--but let prog finish
IP = 244.chr # "\364" # "\xf4" # interrupt process--permanently
BREAK = 243.chr # "\363" # "\xf3" # break
DM = 242.chr # "\362" # "\xf2" # data mark--for connect. cleaning
NOP = 241.chr # "\361" # "\xf1" # nop
SE = 240.chr # "\360" # "\xf0" # end sub negotiation
EOR = 239.chr # "\357" # "\xef" # end of record (transparent mode)
ABORT = 238.chr # "\356" # "\xee" # Abort process
SUSP = 237.chr # "\355" # "\xed" # Suspend process
EOF = 236.chr # "\354" # "\xec" # End of file
SYNCH = 242.chr # "\362" # "\xf2" # for telfunc calls
OPT_BINARY = 0.chr # "\000" # "\x00" # Binary Transmission
OPT_ECHO = 1.chr # "\001" # "\x01" # Echo
OPT_RCP = 2.chr # "\002" # "\x02" # Reconnection
OPT_SGA = 3.chr # "\003" # "\x03" # Suppress Go Ahead
OPT_NAMS = 4.chr # "\004" # "\x04" # Approx Message Size Negotiation
OPT_STATUS = 5.chr # "\005" # "\x05" # Status
OPT_TM = 6.chr # "\006" # "\x06" # Timing Mark
OPT_RCTE = 7.chr # "\a" # "\x07" # Remote Controlled Trans and Echo
OPT_NAOL = 8.chr # "\010" # "\x08" # Output Line Width
OPT_NAOP = 9.chr # "\t" # "\x09" # Output Page Size
OPT_NAOCRD = 10.chr # "\n" # "\x0a" # Output Carriage-Return Disposition
OPT_NAOHTS = 11.chr # "\v" # "\x0b" # Output Horizontal Tab Stops
OPT_NAOHTD = 12.chr # "\f" # "\x0c" # Output Horizontal Tab Disposition
OPT_NAOFFD = 13.chr # "\r" # "\x0d" # Output Formfeed Disposition
OPT_NAOVTS = 14.chr # "\016" # "\x0e" # Output Vertical Tabstops
OPT_NAOVTD = 15.chr # "\017" # "\x0f" # Output Vertical Tab Disposition
OPT_NAOLFD = 16.chr # "\020" # "\x10" # Output Linefeed Disposition
OPT_XASCII = 17.chr # "\021" # "\x11" # Extended ASCII
OPT_LOGOUT = 18.chr # "\022" # "\x12" # Logout
OPT_BM = 19.chr # "\023" # "\x13" # Byte Macro
OPT_DET = 20.chr # "\024" # "\x14" # Data Entry Terminal
OPT_SUPDUP = 21.chr # "\025" # "\x15" # SUPDUP
OPT_SUPDUPOUTPUT = 22.chr # "\026" # "\x16" # SUPDUP Output
OPT_SNDLOC = 23.chr # "\027" # "\x17" # Send Location
OPT_TTYPE = 24.chr # "\030" # "\x18" # Terminal Type
OPT_EOR = 25.chr # "\031" # "\x19" # End of Record
OPT_TUID = 26.chr # "\032" # "\x1a" # TACACS User Identification
OPT_OUTMRK = 27.chr # "\e" # "\x1b" # Output Marking
OPT_TTYLOC = 28.chr # "\034" # "\x1c" # Terminal Location Number
OPT_3270REGIME = 29.chr # "\035" # "\x1d" # Telnet 3270 Regime
OPT_X3PAD = 30.chr # "\036" # "\x1e" # X.3 PAD
OPT_NAWS = 31.chr # "\037" # "\x1f" # Negotiate About Window Size
OPT_TSPEED = 32.chr # " " # "\x20" # Terminal Speed
OPT_LFLOW = 33.chr # "!" # "\x21" # Remote Flow Control
OPT_LINEMODE = 34.chr # "\"" # "\x22" # Linemode
OPT_XDISPLOC = 35.chr # "#" # "\x23" # X Display Location
OPT_OLD_ENVIRON = 36.chr # "$" # "\x24" # Environment Option
OPT_AUTHENTICATION = 37.chr # "%" # "\x25" # Authentication Option
OPT_ENCRYPT = 38.chr # "&" # "\x26" # Encryption Option
OPT_NEW_ENVIRON = 39.chr # "'" # "\x27" # New Environment Option
OPT_EXOPL = 255.chr # "\377" # "\xff" # Extended-Options-List
#
# This method establishes an Telnet connection to host and port specified by
# the RHOST and RPORT options, respectively. After connecting, the banner
# message is read in and stored in the 'banner' attribute. This method has the
# benefit of handling telnet option negotiation.
#
def connect(global = true, verbose = true)
@trace = ''
@recvd = ''
fd = super(global)
self.banner = ''
# Wait for a banner to arrive...
begin
Timeout.timeout(banner_timeout) do
while(true)
buff = recv(fd)
self.banner << buff if buff
if(self.banner =~ @login_regex or self.banner =~ @password_regex)
break
elsif self.banner =~ @busy_regex
# It's about to drop connection anyway -- seen on HP JetDirect telnet server
break
end
end
end
rescue ::Timeout::Error
end
self.banner.strip!
# Return the file descriptor to the caller
fd
end
# Sometimes telnet servers start RSTing if you get them angry.
# This is a short term fix; the problem is that we don't know
# if it's going to reset forever, or just this time, or randomly.
# A better solution is to get the socket connect to try again
# with a little backoff.
def connect_reset_safe
begin
connect
rescue Rex::ConnectionRefused
return :refused
end
return :connected
end
def recv(fd=self.sock, timeout=telnet_timeout)
recv_telnet(fd, timeout.to_f)
end
#
# Handle telnet option negotiation
#
# Appends to the @recvd buffer which is used to tell us whether we're at a
# login prompt, a password prompt, or a working shell.
#
def recv_telnet(fd, timeout)
data = ''
begin
data = fd.get_once(-1, timeout)
return nil if not data or data.length == 0
# combine CR+NULL into CR
data.gsub!(/#{CR}#{NULL}/no, CR)
# combine EOL into "\n"
data.gsub!(/#{EOL}/no, "\n")
data.gsub!(/#{IAC}(
[#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|[#{DO}#{DONT}#{WILL}#{WONT}]
[#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|#{SB}[^#{IAC}]*#{IAC}#{SE}
)/xno) do
m = $1
if m == IAC
IAC
elsif m == AYT
fd.write("YES" + EOL)
''
elsif m[0,1] == DO
if(m[1,1] == OPT_BINARY)
fd.write(IAC + WILL + OPT_BINARY)
else
fd.write(IAC + WONT + m[1,1])
end
''
elsif m[0,1] == DONT
fd.write(IAC + WONT + m[1,1])
''
elsif m[0,1] == WILL
if m[1,1] == OPT_BINARY
fd.write(IAC + DO + OPT_BINARY)
# Disable Echo
elsif m[1,1] == OPT_ECHO
fd.write(IAC + DONT + OPT_ECHO)
elsif m[1,1] == OPT_SGA
fd.write(IAC + DO + OPT_SGA)
else
fd.write(IAC + DONT + m[1,1])
end
''
elsif m[0,1] == WONT
fd.write(IAC + DONT + m[1,1])
''
else
''
end
end
@trace << data
@recvd << data
fd.flush
rescue ::EOFError, ::Errno::EPIPE
end
data
end
#
# Wrappers for getters
#
def banner_timeout
raise NotImplementedError
end
def telnet_timeout
raise NotImplementedError
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Metasploit
module Framework
module Version
MAJOR = 4
MINOR = 10
PATCH = 1
PRERELEASE = 'dev'
end
VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::PATCH}-#{Version::PRERELEASE}"
GEM_VERSION = VERSION.gsub('-', '.pre.')
end
end

View File

@ -1,6 +1,18 @@
# -*- coding: binary -*-
#
# Standard Library
#
require 'fileutils'
#
# Project
#
require 'metasploit/framework/version'
require 'rex/compat'
module Msf
# This class wraps interaction with global configuration that can be used as a
@ -25,16 +37,16 @@ class Config < Hash
['HOME', 'LOCALAPPDATA', 'APPDATA', 'USERPROFILE'].each do |dir|
val = Rex::Compat.getenv(dir)
if (val and File.directory?(val))
return File.join(val, ".msf#{Msf::Framework::Major}")
return File.join(val, ".msf#{Metasploit::Framework::Version::MAJOR}")
end
end
begin
# First we try $HOME/.msfx
File.expand_path("~#{FileSep}.msf#{Msf::Framework::Major}")
File.expand_path("~#{FileSep}.msf#{Metasploit::Framework::Version::MAJOR}")
rescue ::ArgumentError
# Give up and install root + ".msfx"
InstallRoot + ".msf#{Msf::Framework::Major}"
InstallRoot + ".msf#{Metasploit::Framework::Version::MAJOR}"
end
end

View File

@ -9,9 +9,10 @@ module Msf
# Ensure the module cache is accurate
self.modules.refresh_cache_from_database
# Initialize the default module search paths
if (Msf::Config.module_directory)
self.modules.add_module_path(Msf::Config.module_directory, opts)
add_engine_module_paths(Rails.application, opts)
Rails.application.railties.engines.each do |engine|
add_engine_module_paths(engine, opts)
end
# Initialize the user module search path
@ -27,6 +28,25 @@ module Msf
}
end
end
private
# Add directories `engine.paths['modules']` from `engine`.
#
# @param engine [Rails::Engine] a rails engine or application
# @param options [Hash] options for {Msf::ModuleManager::ModulePaths#add_module_paths}
# @return [void]
def add_engine_module_paths(engine, options={})
modules_paths = engine.paths['modules']
if modules_paths
modules_directories = modules_paths.existent_directories
modules_directories.each do |modules_directory|
modules.add_module_path(modules_directory, options)
end
end
end
end
end
end

View File

@ -2,7 +2,8 @@
require 'open3'
require 'fileutils'
require 'rex/proto/ntlm/crypt'
require 'metasploit/framework/jtr/cracker'
require 'metasploit/framework/jtr/wordlist'
module Msf
@ -24,243 +25,28 @@ module Auxiliary::JohnTheRipper
register_options(
[
OptPath.new('JOHN_BASE', [false, 'The directory containing John the Ripper (src, run, doc)']),
OptPath.new('JOHN_PATH', [false, 'The absolute path to the John the Ripper executable']),
OptPath.new('Wordlist', [false, 'The path to an optional Wordlist']),
OptBool.new('Munge',[false, 'Munge the Wordlist (Slower)', false])
OptPath.new('CONFIG', [false, 'The path to a John config file to use instead of the default']),
OptPath.new('CUSTOM_WORDLIST', [false, 'The path to an optional custom wordlist']),
OptInt.new('ITERATION_TIMOUT', [false, 'The max-run-time for each iteration of cracking']),
OptPath.new('JOHN_PATH', [false, 'The absolute path to the John the Ripper executable']),
OptBool.new('MUTATE', [false, 'Apply common mutations to the Wordlist (SLOW)', false]),
OptPath.new('POT', [false, 'The path to a John POT file to use instead of the default']),
OptBool.new('USE_CREDS', [false, 'Use existing credential data saved in the database', true]),
OptBool.new('USE_DB_INFO', [false, 'Use looted database schema info to seed the wordlist', true]),
OptBool.new('USE_DEFAULT_WORDLIST', [false, 'Use the default metasploit wordlist', true]),
OptBool.new('USE_HOSTNAMES', [false, 'Seed the wordlist with hostnames from the workspace', true]),
OptBool.new('USE_ROOT_WORDS', [false, 'Use the Common Root Words Wordlist', true])
], Msf::Auxiliary::JohnTheRipper
)
@run_path = nil
@john_path = ::File.join(Msf::Config.data_directory, "john")
autodetect_platform
end
# @return [String] the run path instance variable if the platform is detectable, nil otherwise.
def autodetect_platform
return @run_path if @run_path
cpuinfo_base = ::File.join(Msf::Config.data_directory, "cpuinfo")
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 ||= "run.win32.sse2/john.exe"
when /mmx/
@run_path ||= "run.win32.mmx/john.exe"
else
@run_path ||= "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 ||= "run.linux.x64.mmx/john"
else
@run_path ||= "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 ||= "run.linux.x86.sse2/john"
when /mmx/
@run_path ||= "run.linux.x86.mmx/john"
else
@run_path ||= "run.linux.x86.any/john"
end
end
end
return @run_path
end
def john_session_id
@session_id ||= ::Rex::Text.rand_text_alphanumeric(8)
end
def john_pot_file
::File.join( ::Msf::Config.config_directory, "john.pot" )
end
def john_cracked_passwords
ret = {}
return ret if not ::File.exist?(john_pot_file)
::File.open(john_pot_file, "rb") do |fd|
fd.each_line do |line|
hash,clear = line.sub(/\r?\n$/, '').split(",", 2)
ret[hash] = clear
end
end
ret
end
def john_show_passwords(hfile, format=nil)
res = {:cracked => 0, :uncracked => 0, :users => {} }
john_command = john_binary_path
if john_command.nil?
print_error("John the Ripper executable not found")
return res
end
pot = john_pot_file
conf = ::File.join(john_base_path, "confs", "john.conf")
cmd = [ john_command, "--show", "--conf=#{conf}", "--pot=#{pot}", hfile]
if format
cmd << "--format=" + format
end
if RUBY_VERSION =~ /^1\.8\./
cmd = cmd.join(" ")
end
::IO.popen(cmd, "rb") do |fd|
fd.each_line do |line|
line.chomp!
print_status(line)
if line =~ /(\d+) password hash(es)* cracked, (\d+) left/m
res[:cracked] = $1.to_i
res[:uncracked] = $2.to_i
end
# XXX: If the password had : characters in it, we're screwed
bits = line.split(':', -1)
# Skip blank passwords
next if not bits[2]
if (format== 'lm' or format == 'nt')
res[ :users ][ bits[0] ] = bits[1]
else
bits.last.chomp!
res[ :users ][ bits[0] ] = bits.drop(1)
end
end
end
res
end
def john_unshadow(passwd_file,shadow_file)
retval=""
john_command = john_binary_path
if john_command.nil?
print_error("John the Ripper executable not found")
return nil
end
if File.exists?(passwd_file)
unless File.readable?(passwd_file)
print_error("We do not have permission to read #{passwd_file}")
return nil
end
else
print_error("File does not exist: #{passwd_file}")
return nil
end
if File.exists?(shadow_file)
unless File.readable?(shadow_file)
print_error("We do not have permission to read #{shadow_file}")
return nil
end
else
print_error("File does not exist: #{shadow_file}")
return nil
end
cmd = [ john_command.gsub(/john$/, "unshadow"), passwd_file , shadow_file ]
if RUBY_VERSION =~ /^1\.8\./
cmd = cmd.join(" ")
end
::IO.popen(cmd, "rb") do |fd|
fd.each_line do |line|
retval << line
end
end
return retval
end
def john_wordlist_path
# We ship it under wordlists/
path = ::File.join(john_base_path, "wordlists", "password.lst")
# magnumripper/JohnTheRipper repo keeps it under run/
unless ::File.file? path
path = ::File.join(john_base_path, "run", "password.lst")
end
path
end
def john_binary_path
path = nil
if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH'])
path = datastore['JOHN_PATH']
::FileUtils.chmod(0755, path) rescue nil
return path
end
if not @run_path
if ::RUBY_PLATFORM =~ /mingw|cygwin|mswin/
::File.join(john_base_path, "john.exe")
else
path = ::File.join(john_base_path, "john")
::FileUtils.chmod(0755, path) rescue nil
end
else
path = ::File.join(john_base_path, @run_path)
::FileUtils.chmod(0755, path) rescue nil
end
if path and ::File.exists?(path)
return path
end
path = Rex::FileUtils.find_full_path("john") ||
Rex::FileUtils.find_full_path("john.exe")
end
def john_base_path
if datastore['JOHN_BASE'] and ::File.directory?(datastore['JOHN_BASE'])
return datastore['JOHN_BASE']
end
if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH'])
return ::File.dirname( datastore['JOHN_PATH'] )
end
@john_path
end
def john_expand_word(str)
res = [str]
str.split(/\W+/) {|w| res << w }
res.uniq
end
# @param pwd [String] Password recovered from cracking an LM hash
# @param hash [String] NTLM hash for this password
# @return [String] `pwd` converted to the correct case to match the
# given NTLM hash
# @return [nil] if no case matches the NT hash. This can happen when
# `pwd` came from a john run that only cracked half of the LM hash
def john_lm_upper_to_ntlm(pwd, hash)
pwd = pwd.upcase
hash = hash.upcase
@ -273,179 +59,41 @@ module Auxiliary::JohnTheRipper
end
def john_crack(hfile, opts={})
res = {:cracked => 0, :uncracked => 0, :users => {} }
john_command = john_binary_path
if john_command.nil?
print_error("John the Ripper executable not found")
return nil
end
# Don't bother making a log file, we'd just have to rm it when we're
# done anyway.
cmd = [ john_command, "--session=" + john_session_id, "--nolog"]
if opts[:conf]
cmd << ( "--conf=" + opts[:conf] )
else
cmd << ( "--conf=" + ::File.join(john_base_path, "confs", "john.conf") )
end
if opts[:pot]
cmd << ( "--pot=" + opts[:pot] )
else
cmd << ( "--pot=" + john_pot_file )
end
if opts[:format]
cmd << ( "--format=" + opts[:format] )
end
if opts[:wordlist]
cmd << ( "--wordlist=" + opts[:wordlist] )
end
if opts[:incremental]
cmd << ( "--incremental=" + opts[:incremental] )
end
if opts[:single]
cmd << ( "--single=" + opts[:single] )
end
if opts[:rules]
cmd << ( "--rules=" + opts[:rules] )
end
cmd << hfile
if RUBY_VERSION =~ /^1\.8\./
cmd = cmd.join(" ")
end
::IO.popen(cmd, "rb") do |fd|
fd.each_line do |line|
print_status("Output: #{line.strip}")
end
end
res
# This method creates a new {Metasploit::Framework::JtR::Cracker} and populates
# some of the attributes based on the module datastore options.
#
# @return [nilClass] if there is no active framework db connection
# @return [Metasploit::Framework::JtR::Cracker] if it successfully creates a JtR Cracker object
def new_john_cracker
return nil unless framework.db.active
Metasploit::Framework::JtR::Cracker.new(
config: datastore['CONFIG'],
john_path: datastore['JOHN_PATH'],
max_runtime: datastore['ITERATION_TIMEOUT'],
pot: datastore['POT'],
wordlist: datastore['CUSTOM_WORDLIST']
)
end
def build_seed
seed = []
#Seed the wordlist with Database , Table, and Instance Names
count = 0
schemas = myworkspace.notes.where('ntype like ?', '%.schema%')
unless schemas.nil? or schemas.empty?
schemas.each do |anote|
seed << anote.data['DBName']
count += 1
anote.data['Tables'].each do |table|
seed << table['TableName']
count += 1
table['Columns'].each do |column|
seed << column['ColumnName']
count += 1
end
end
end
end
print_status "Seeding wordlist with DB schema info... #{count} words added"
count = 0
instances = myworkspace.notes.find(:all, :conditions => ['ntype=?', 'mssql.instancename'])
unless instances.nil? or instances.empty?
instances.each do |anote|
seed << anote.data['InstanceName']
count += 1
end
end
print_status "Seeding with MSSQL Instance Names....#{count} words added"
count = 0
# Seed the wordlist with usernames, passwords, and hostnames
myworkspace.hosts.find(:all).each do |o|
if o.name
seed << john_expand_word( o.name )
count += 1
end
end
print_status "Seeding with hostnames....#{count} words added"
count = 0
myworkspace.creds.each do |o|
if o.user
seed << john_expand_word( o.user )
count +=1
end
if (o.pass and o.ptype !~ /hash/)
seed << john_expand_word( o.pass )
count += 1
end
end
print_status "Seeding with found credentials....#{count} words added"
count = 0
# Grab any known passwords out of the john.pot file
john_cracked_passwords.values do |v|
seed << v
count += 1
end
print_status "Seeding with cracked passwords from John....#{count} words added"
count = 0
#Grab the default John Wordlist
john = File.open(john_wordlist_path, "rb")
john.each_line do |line|
seed << line.chomp
count += 1
end
print_status "Seeding with default John wordlist...#{count} words added"
count = 0
if datastore['Wordlist']
wordlist= File.open(datastore['Wordlist'], "rb")
wordlist.each_line do |line|
seed << line.chomp
count ==1
end
print_status "Seeding from user supplied wordlist...#{count} words added"
end
unless seed.empty?
seed.flatten!
seed.uniq!
if datastore['Munge']
mungedseed=[]
seed.each do |word|
munged = word.gsub(/[sS]/, "$").gsub(/[aA]/,"@").gsub(/[oO]/,"0")
mungedseed << munged
munged.gsub!(/[eE]/, "3")
munged.gsub!(/[tT]/, "7")
mungedseed << munged
end
print_status "Adding #{mungedseed.count} words from munging..."
seed << mungedseed
seed.flatten!
seed.uniq!
end
end
print_status "De-duping the wordlist...."
print_status("Wordlist Seeded with #{seed.length} words")
return seed
# This method instantiates a {Metasploit::Framework::JtR::Wordlist}, writes the data
# out to a file and returns the {rex::quickfile} object.
#
# @return [nilClass] if there is no active framework db connection
# @return [Rex::Quickfile] if it successfully wrote the wordlist to a file
def wordlist_file
return nil unless framework.db.active
wordlist = Metasploit::Framework::JtR::Wordlist.new(
custom_wordlist: datastore['CUSTOM_WORDLIST'],
mutate: datastore['MUTATE'],
use_creds: datastore['USE_CREDS'],
use_db_info: datastore['USE_DB_INFO'],
use_default_wordlist: datastore['USE_DEFAULT_WORDLIST'],
use_hostnames: datastore['USE_HOSTNAMES'],
use_common_root: datastore['USE_ROOT_WORDS'],
workspace: myworkspace
)
wordlist.to_file
end
end
end

View File

@ -21,6 +21,10 @@ module Auxiliary::Login
def initialize(info = {})
super
create_login_ivars
end
def create_login_ivars
# Appended to by each read and gets reset after each send. Doing it
# this way lets us deal with partial reads in the middle of expect
# strings, e.g., the first recv returns "Pa" and the second returns

View File

@ -19,6 +19,5 @@ require 'msf/core/auxiliary/login'
require 'msf/core/auxiliary/rservices'
require 'msf/core/auxiliary/cisco'
require 'msf/core/auxiliary/nmap'
require 'msf/core/auxiliary/jtr'
require 'msf/core/auxiliary/iax2'
require 'msf/core/auxiliary/pii'

View File

@ -8,10 +8,13 @@ module Msf
###
module Auxiliary::Report
extend Metasploit::Framework::Require
optionally_include_metasploit_credential_creation
def initialize(info = {})
super
# This method overrides the method from Metasploit::Credential to check for an active db
def active_db?
framework.db.active
end
# Shortcut method for detecting when the DB is active
@ -23,6 +26,18 @@ module Auxiliary::Report
@myworkspace = framework.db.find_workspace(self.workspace)
end
# This method safely get the workspace ID. It handles if the db is not active
#
# @return [NilClass] if there is no DB connection
# @return [Fixnum] the ID of the current {Mdm::Workspace}
def myworkspace_id
if framework.db.active
myworkspace.id
else
nil
end
end
def mytask
if self[:task]
return self[:task].record
@ -387,6 +402,9 @@ module Auxiliary::Report
print_status "Collecting #{cred_opts[:user]}:#{cred_opts[:pass]}"
framework.db.report_auth_info(cred_opts)
end
end
end

View File

@ -7,7 +7,6 @@
require 'csv'
require 'tmpdir'
require 'uri'
require 'zip'
#
#
@ -56,6 +55,7 @@ require 'rex/parser/retina_xml'
# Project
#
require 'metasploit/framework/require'
require 'msf/core/db_manager/import_msf_xml'
module Msf
@ -156,7 +156,10 @@ end
#
###
class DBManager
extend Metasploit::Framework::Require
include Msf::DBManager::ImportMsfXml
optionally_include_metasploit_credential_creation
def rfc3330_reserved(ip)
case ip.class.to_s
@ -1371,8 +1374,6 @@ class DBManager
=end
ntype = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required")
data = opts[:data]
method = nil
args = []
note = nil
conditions = { :ntype => ntype }
@ -1381,15 +1382,7 @@ class DBManager
case mode
when :unique
notes = wspace.notes.where(conditions)
# Only one note of this type should exist, make a new one if it
# isn't there. If it is, grab it and overwrite its data.
if notes.empty?
note = wspace.notes.new(conditions)
else
note = notes[0]
end
note = wspace.notes.where(conditions).first_or_initialize
note.data = data
when :unique_data
notes = wspace.notes.where(conditions)
@ -2184,9 +2177,15 @@ class DBManager
# @return [Integer] ID of created report
def report_report(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
created = opts.delete(:created_at)
updated = opts.delete(:updated_at)
state = opts.delete(:state)
::ActiveRecord::Base.connection_pool.with_connection {
report = Report.new(opts)
report.created_at = created
report.updated_at = updated
unless report.valid?
errors = report.errors.full_messages.join('; ')
raise RuntimeError "Report to be imported is not valid: #{errors}"
@ -2201,10 +2200,14 @@ class DBManager
# Creates a ReportArtifact based on passed parameters.
# @param opts [Hash] of ReportArtifact attributes
def report_artifact(opts)
return if not active
artifacts_dir = Report::ARTIFACT_DIR
tmp_path = opts[:file_path]
artifact_name = File.basename tmp_path
new_path = File.join(artifacts_dir, artifact_name)
created = opts.delete(:created_at)
updated = opts.delete(:updated_at)
unless File.exists? tmp_path
raise DBImportError 'Report artifact file to be imported does not exist.'
@ -2222,6 +2225,9 @@ class DBManager
FileUtils.copy(tmp_path, new_path)
opts[:file_path] = new_path
artifact = ReportArtifact.new(opts)
artifact.created_at = created
artifact.updated_at = updated
unless artifact.valid?
errors = artifact.errors.full_messages.join('; ')
raise RuntimeError "Artifact to be imported is not valid: #{errors}"
@ -2906,29 +2912,36 @@ class DBManager
data = ""
::File.open(filename, 'rb') do |f|
data = f.read(4)
# This check is the largest (byte-wise) that we need to do
# since the other 4-byte checks will be subsets of this larger one.
data = f.read(Metasploit::Credential::Exporter::Pwdump::FILE_ID_STRING.size)
end
if data.nil?
raise DBImportError.new("Zero-length file")
end
case data[0,4]
when "PK\x03\x04"
data = Zip::ZipFile.open(filename)
when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4"
data = PacketFu::PcapFile.new(:filename => filename)
if data.index(Metasploit::Credential::Exporter::Pwdump::FILE_ID_STRING)
data = ::File.open(filename, 'rb')
else
::File.open(filename, 'rb') do |f|
sz = f.stat.size
data = f.read(sz)
case data[0,4]
when "PK\x03\x04"
data = Zip::File.open(filename)
when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4"
data = PacketFu::PcapFile.new(:filename => filename)
else
::File.open(filename, 'rb') do |f|
sz = f.stat.size
data = f.read(sz)
end
end
end
if block
import(args.merge(:data => data)) { |type,data| yield type,data }
else
import(args.merge(:data => data))
end
end
# A dispatcher method that figures out the data's file type,
@ -2937,7 +2950,6 @@ class DBManager
# is unknown.
def import(args={}, &block)
data = args[:data] || args['data']
wspace = args[:wspace] || args['wspace'] || workspace
ftype = import_filetype_detect(data)
yield(:filetype, @import_filedata[:type]) if block
self.send "import_#{ftype}".to_sym, args, &block
@ -2958,6 +2970,7 @@ class DBManager
# :ip_list
# :libpcap
# :mbsa_xml
# :msf_cred_dump_zip
# :msf_pwdump
# :msf_xml
# :msf_zip
@ -2979,9 +2992,11 @@ class DBManager
# :wapiti_xml
#
# If there is no match, an error is raised instead.
#
# @raise DBImportError if the type can't be detected
def import_filetype_detect(data)
if data and data.kind_of? Zip::ZipFile
if data and data.kind_of? Zip::File
if data.entries.empty?
raise DBImportError.new("The zip file provided is empty.")
end
@ -2991,6 +3006,11 @@ class DBManager
@import_filedata[:zip_basename] = @import_filedata[:zip_filename].gsub(/\.zip$/,"")
@import_filedata[:zip_entry_names] = data.entries.map {|x| x.name}
if @import_filedata[:zip_entry_names].include?(Metasploit::Credential::Importer::Zip::MANIFEST_FILE_NAME)
@import_filedata[:type] = "Metasploit Credential Dump"
return :msf_cred_dump_zip
end
xml_files = @import_filedata[:zip_entry_names].grep(/^(.*)\.xml$/)
# TODO This check for our zip export should be more extensive
@ -3014,6 +3034,12 @@ class DBManager
return :libpcap
end
# msfpwdump
if data.present? && data.kind_of?(::File)
@import_filedata[:type] = "Metasploit PWDump Export"
return :msf_pwdump
end
# This is a text string, lets make sure its treated as binary
data = data.unpack("C*").pack("C*")
if data and data.to_s.strip.length == 0
@ -3376,7 +3402,7 @@ class DBManager
end
end # tcp or udp
inspect_single_packet(pkt,wspace,args[:task])
inspect_single_packet(pkt,wspace,args)
end # data.body.map
@ -3389,16 +3415,17 @@ class DBManager
# Do all the single packet analysis we can while churning through the pcap
# the first time. Multiple packet inspection will come later, where we can
# do stream analysis, compare requests and responses, etc.
def inspect_single_packet(pkt,wspace,task=nil)
def inspect_single_packet(pkt,wspace,args)
if pkt.is_tcp? or pkt.is_udp?
inspect_single_packet_http(pkt,wspace,task)
inspect_single_packet_http(pkt,wspace,args)
end
end
# Checks for packets that are headed towards port 80, are tcp, contain an HTTP/1.0
# line, contains an Authorization line, contains a b64-encoded credential, and
# extracts it. Reports this credential and solidifies the service as HTTP.
def inspect_single_packet_http(pkt,wspace,task=nil)
def inspect_single_packet_http(pkt,wspace,args)
task = args.fetch(:task, nil)
# First, check the server side (data from port 80).
if pkt.is_tcp? and pkt.tcp_src == 80 and !pkt.payload.nil? and !pkt.payload.empty?
if pkt.payload =~ /^HTTP\x2f1\x2e[01]/n
@ -3442,17 +3469,37 @@ class DBManager
:name => "http",
:task => task
)
report_auth_info(
:workspace => wspace,
:host => pkt.ip_daddr,
:port => pkt.tcp_dst,
:proto => "tcp",
:type => "password",
:active => true, # Once we can build a stream, determine if the auth was successful. For now, assume it is.
:user => user,
:pass => pass,
:task => task
)
service_data = {
address: pkt.ip_daddr,
port: pkt.tcp_dst,
service_name: 'http',
protocol: 'tcp',
workspace_id: wspace.id
}
service_data[:task_id] = task.id if task
filename = args[:filename]
credential_data = {
origin_type: :import,
private_data: pass,
private_type: :password,
username: user,
filename: filename
}
credential_data.merge!(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}
login_data.merge!(service_data)
create_credential_login(login_data)
# That's all we want to know from this service.
return :something_significant
end
@ -3496,109 +3543,15 @@ class DBManager
end
end
#
# Metasploit PWDump Export
#
# This file format is generated by the db_export -f pwdump and
# the Metasploit Express and Pro report types of "PWDump."
#
# This particular block scheme is temporary, since someone is
# bound to want to import gigantic lists, so we'll want a
# stream parser eventually (just like the other non-nmap formats).
#
# The file format is:
# # 1.2.3.4:23/tcp (telnet)
# username password
# user2 p\x01a\x02ss2
# <BLANK> pass3
# user3 <BLANK>
# smbuser:sid:lmhash:nthash:::
#
# Note the leading hash for the host:port line. Note also all usernames
# and passwords must be in 7-bit ASCII (character sequences of "\x01"
# will be interpolated -- this includes spaces, which must be notated
# as "\x20". Blank usernames or passwords should be <BLANK>.
#
# Perform in an import of an msfpwdump file
def import_msf_pwdump(args={}, &block)
data = args[:data]
wspace = args[:wspace] || workspace
bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
last_host = nil
addr = nil
port = nil
proto = nil
sname = nil
ptype = nil
active = false # Are there cases where imported creds are good? I just hate trusting the import right away.
data.each_line do |line|
case line
when /^[\s]*#/ # Comment lines
if line[/^#[\s]*([0-9.]+):([0-9]+)(\x2f(tcp|udp))?[\s]*(\x28([^\x29]*)\x29)?/n]
addr = $1
port = $2
proto = $4
sname = $6
end
when /^[\s]*Warning:/
# Discard warning messages.
next
# SMB Hash
when /^[\s]*([^\s:]+):[0-9]+:([A-Fa-f0-9]+:[A-Fa-f0-9]+):[^\s]*$/
user = ([nil, "<BLANK>"].include?($1)) ? "" : $1
pass = ([nil, "<BLANK>"].include?($2)) ? "" : $2
ptype = "smb_hash"
# SMB Hash
when /^[\s]*([^\s:]+):([0-9]+):NO PASSWORD\*+:NO PASSWORD\*+[^\s]*$/
user = ([nil, "<BLANK>"].include?($1)) ? "" : $1
pass = ""
ptype = "smb_hash"
# SMB Hash with cracked plaintext, or just plain old plaintext
when /^[\s]*([^\s:]+):(.+):[A-Fa-f0-9]*:[A-Fa-f0-9]*:::$/
user = ([nil, "<BLANK>"].include?($1)) ? "" : $1
pass = ([nil, "<BLANK>"].include?($2)) ? "" : $2
ptype = "password"
# Must be a user pass
when /^[\s]*([\x21-\x7f]+)[\s]+([\x21-\x7f]+)?/n
user = ([nil, "<BLANK>"].include?($1)) ? "" : dehex($1)
pass = ([nil, "<BLANK>"].include?($2)) ? "" : dehex($2)
ptype = "password"
else # Some unknown line not broken by a space.
next
end
next unless [addr,port,user,pass].compact.size == 4
next unless ipv46_validator(addr) # Skip Malformed addrs
next unless port[/^[0-9]+$/] # Skip malformed ports
if bl.include? addr
next
else
yield(:address,addr) if block and addr != last_host
last_host = addr
end
cred_info = {
:host => addr,
:port => port,
:user => user,
:pass => pass,
:type => ptype,
:workspace => wspace,
:task => args[:task]
}
cred_info[:proto] = proto if proto
cred_info[:sname] = sname if sname
cred_info[:active] = active
report_auth_info(cred_info)
user = pass = ptype = nil
end
filename = File.basename(args[:data].path)
wspace = args[:wspace] || workspace
origin = Metasploit::Credential::Origin::Import.create!(filename: filename)
importer = Metasploit::Credential::Importer::Pwdump.new(input: args[:data], workspace: wspace, filename: filename, origin:origin)
importer.import!
importer.input.close unless importer.input.closed?
end
# If hex notation is present, turn them into a character.
@ -3649,7 +3602,7 @@ class DBManager
# XXX: Refactor so it's not quite as sanity-blasting.
def import_msf_zip(args={}, &block)
data = args[:data]
wpsace = args[:wspace] || workspace
wspace = args[:wspace] || workspace
bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
new_tmp = ::File.join(Dir::tmpdir,"msf","imp_#{Rex::Text::rand_text_alphanumeric(4)}",@import_filedata[:zip_basename])
@ -3680,9 +3633,10 @@ class DBManager
}
data.entries.each do |e|
target = ::File.join(@import_filedata[:zip_tmp],e.name)
target = ::File.join(@import_filedata[:zip_tmp], e.name)
data.extract(e,target)
if target =~ /^.*.xml$/
if target =~ /\.xml\z/
target_data = ::File.open(target, "rb") {|f| f.read 1024}
if import_filetype_detect(target_data) == :msf_xml
@import_filedata[:zip_extracted_xml] = target
@ -3690,6 +3644,16 @@ class DBManager
end
end
# Import any creds if there are some in the import file
Dir.entries(@import_filedata[:zip_tmp]).each do |entry|
if entry =~ /^.*#{Regexp.quote(Metasploit::Credential::Exporter::Core::CREDS_DUMP_FILE_IDENTIFIER)}.*/
manifest_file_path = File.join(@import_filedata[:zip_tmp], entry, Metasploit::Credential::Importer::Zip::MANIFEST_FILE_NAME)
if File.exists? manifest_file_path
import_msf_cred_dump(manifest_file_path, wspace)
end
end
end
# This will kick the newly-extracted XML file through
# the import_file process all over again.
if @import_filedata[:zip_extracted_xml]
@ -3856,6 +3820,31 @@ class DBManager
end
end
# Import credentials given a path to a valid manifest file
#
# @param creds_dump_manifest_path [String]
# @param workspace [Mdm::Workspace] Default: {#workspace}
# @return [void]
def import_msf_cred_dump(creds_dump_manifest_path, workspace)
manifest_file = File.open(creds_dump_manifest_path)
origin = Metasploit::Credential::Origin::Import.create!(filename: File.basename(creds_dump_manifest_path))
importer = Metasploit::Credential::Importer::Core.new(workspace: workspace, input: manifest_file, origin: origin)
importer.import!
end
# Import credentials given a path to a valid manifest file
#
# @option args [String] :filename
# @option args [Mdm::Workspace] :wspace Default: {#workspace}
# @return [void]
def import_msf_cred_dump_zip(args = {})
wspace = args[:wspace] || workspace
origin = Metasploit::Credential::Origin::Import.create!(filename: File.basename(args[:filename]))
importer = Metasploit::Credential::Importer::Zip.new(workspace: wspace, input: File.open(args[:filename]), origin: origin)
importer.import!
nil
end
# @param report [REXML::Element] to be imported
# @param args [Hash]
# @param base_dir [String]

View File

@ -28,175 +28,19 @@ class Export
true
end
# Creates the PWDUMP text file. smb_hash and ssh_key credentials are
# treated specially -- all other ptypes are treated as plain text.
#
# Some day in the very near future, this file format will be importable --
# the comment preceding the credential is always in the format of IPAddr:Port/Proto (name),
# so it should be a simple matter to read them back in and store credentials
# in the right place. Finally, this format is already parsable by John the Ripper,
# so hashes can be bruteforced offline.
# Performs an export of the workspace's `Metasploit::Credential::Login` objects in pwdump format
# @param path [String] the path on the local filesystem where the exported data will be written
# @return [void]
def to_pwdump_file(path, &block)
yield(:status, "start", "password dump") if block_given?
creds = extract_credentials
report_file = ::File.open(path, "wb")
report_file.write "# Metasploit PWDump Export v1\n"
report_file.write "# Generated: #{Time.now.utc}\n"
report_file.write "# Project: #{myworkspace.name}\n"
report_file.write "#\n"
report_file.write "#" * 40; report_file.write "\n"
exporter = Metasploit::Credential::Exporter::Pwdump.new(workspace: workspace)
count = count_credentials("smb_hash",creds)
scount = creds.has_key?("smb_hash") ? creds["smb_hash"].size : 0
yield(:status, "start", "LM/NTLM Hash dump") if block_given?
report_file.write "# LM/NTLM Hashes (%d services, %d hashes)\n" % [scount, count]
write_credentials("smb_hash",creds,report_file)
count = count_credentials("smb_netv1_hash",creds)
scount = creds.has_key?("smb_netv1_hash") ? creds["smb_netv1_hash"].size : 0
yield(:status, "start", "NETLMv1/NETNTLMv1 Hash dump") if block_given?
report_file.write "# NETLMv1/NETNTLMv1 Hashes (%d services, %d hashes)\n" % [scount, count]
write_credentials("smb_netv1_hash",creds,report_file)
count = count_credentials("smb_netv2_hash",creds)
scount = creds.has_key?("smb_netv2_hash") ? creds["smb_netv2_hash"].size : 0
yield(:status, "start", "NETLMv2/NETNTLMv2 Hash dump") if block_given?
report_file.write "# NETLMv2/NETNTLMv2 Hashes (%d services, %d hashes)\n" % [scount, count]
write_credentials("smb_netv2_hash",creds,report_file)
count = count_credentials("ssh_key",creds)
scount = creds.has_key?("ssh_key") ? creds["ssh_key"].size : 0
yield(:status, "start", "SSH Key dump") if block_given?
report_file.write "# SSH Private Keys (%d services, %d keys)\n" % [scount, count]
write_credentials("ssh_key",creds,report_file)
count = count_credentials("text",creds)
scount = creds.has_key?("text") ? creds["text"].size : 0
yield(:status, "start", "Plaintext Credential dump") if block_given?
report_file.write "# Plaintext Credentials (%d services, %d credentials)\n" % [scount, count]
write_credentials("text",creds,report_file)
report_file.flush
report_file.close
yield(:status, "complete", "password dump") if block_given?
File.open(path, 'w') do |file|
file << exporter.rendered_output
end
true
end
# Counts the total number of credentials for its type.
def count_credentials(ptype,creds)
sz = 0
if creds[ptype]
creds[ptype].each_pair { |svc, data| data.each { |c| sz +=1 } }
end
return sz
end
# Formats credentials according to their type, and writes it out to the
# supplied report file. Note for reimporting: Blank values are <BLANK>
def write_credentials(ptype,creds,report_file)
if creds[ptype]
creds[ptype].each_pair do |svc, data|
report_file.write "# #{svc}\n"
case ptype
when "smb_hash"
data.each do |c|
user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user
pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : c.pass
report_file.write "%s:%d:%s:::\n" % [user,c.id,pass]
end
when "smb_netv1_hash"
data.each do |c|
user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user
pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : c.pass
report_file.write "%s::%s\n" % [user,pass]
end
when "smb_netv2_hash"
data.each do |c|
user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user
pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : c.pass
if pass != "<BLANK>"
pass = (c.pass.upcase =~ /^[\x20-\x7e]*:[A-F0-9]{48}:[A-F0-9]{50,}/nm) ? c.pass : "<BLANK>"
end
if pass == "<BLANK>"
# Basically this is an error (maybe around [\x20-\x7e] in regex) above
report_file.write(user + "::" + pass + ":")
report_file.write(pass + ":" + pass + ":" + pass + "\n")
else
datas = pass.split(":")
if datas[1] != "00" * 24
report_file.write "# netlmv2\n"
report_file.write(user + "::" + datas[0] + ":")
report_file.write(datas[3] + ":" + datas[1][0,32] + ":" + datas[1][32,16] + "\n")
end
report_file.write "# netntlmv2\n"
report_file.write(user + "::" + datas[0] + ":")
report_file.write(datas[3] + ":" + datas[2][0,32] + ":" + datas[2][32..-1] + "\n")
end
end
when "ssh_key"
data.each do |c|
if ::File.exists?(c.pass) && ::File.readable?(c.pass)
user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user
key = ::File.open(c.pass) {|f| f.read f.stat.size}
key_id = (c.proof && c.proof[/^KEY=/]) ? c.proof[4,47] : "<NO-ID>"
report_file.write "#{user} '#{key_id}'\n"
report_file.write key
report_file.write "\n" unless key[-1,1] == "\n"
# Report file missing / permissions issues in the report itself.
elsif !::File.exists?(c.pass)
report_file.puts "Warning: missing private key file '#{c.pass}'."
else
report_file.puts "Warning: could not read the private key '#{c.pass}'."
end
end
when "text"
data.each do |c|
user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : Rex::Text.ascii_safe_hex(c.user, true)
pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : Rex::Text.ascii_safe_hex(c.pass, true)
report_file.write "%s:%s:::\n" % [user,pass]
end
end
report_file.flush
end
else
report_file.write "# No credentials for this type were discovered.\n"
end
report_file.write "#" * 40; report_file.write "\n"
end
# Extracts credentials and organizes by type, then by host, and finally by individual
# credential data. Will look something like:
#
# {"smb_hash" => {"host1:445" => [user1,user2,user3], "host2:445" => [user4,user5]}},
# {"ssh_key" => {"host3:22" => [user10,user20]}},
# {"text" => {"host4:23" => [user100,user101]}}
#
# This hash of hashes of arrays is, in turn, consumed by gen_export_pwdump.
def extract_credentials
creds = Hash.new
creds["ssh_key"] = {}
creds["smb_hash"] = {}
creds["text"] = {}
myworkspace.each_cred do |cred|
next unless host_allowed?(cred.service.host.address)
# Skip anything that's not associated with a specific host and port
next unless (cred.service && cred.service.host && cred.service.host.address && cred.service.port)
# TODO: Toggle active/all
next unless cred.active
svc = "%s:%d/%s (%s)" % [cred.service.host.address,cred.service.port,cred.service.proto,cred.service.name]
case cred.ptype
when /^password/
ptype = "text"
else
ptype = cred.ptype
end
creds[ptype] ||= {}
creds[ptype][svc] ||= []
creds[ptype][svc] << cred
end
return creds
end
def to_xml_file(path, &block)
@ -205,7 +49,7 @@ class Export
report_file = ::File.open(path, "wb")
report_file.write %Q|<?xml version="1.0" encoding="UTF-8"?>\n|
report_file.write %Q|<MetasploitV4>\n|
report_file.write %Q|<MetasploitV5>\n|
report_file.write %Q|<generated time="#{Time.now.utc}" user="#{myusername}" project="#{myworkspace.name.gsub(/[^A-Za-z0-9\x20]/n,"_")}" product="framework"/>\n|
yield(:status, "start", "hosts") if block_given?
@ -226,12 +70,6 @@ class Export
extract_service_info(report_file)
report_file.write %Q|</services>\n|
yield(:status, "start", "credentials") if block_given?
report_file.write %Q|<credentials>\n|
report_file.flush
extract_credential_info(report_file)
report_file.write %Q|</credentials>\n|
yield(:status, "start", "web sites") if block_given?
report_file.write %Q|<web_sites>\n|
report_file.flush
@ -263,7 +101,7 @@ class Export
report_file.write %Q|</module_details>\n|
report_file.write %Q|</MetasploitV4>\n|
report_file.write %Q|</MetasploitV5>\n|
report_file.flush
report_file.close
@ -277,7 +115,6 @@ class Export
extract_host_entries
extract_event_entries
extract_service_entries
extract_credential_entries
extract_note_entries
extract_vuln_entries
extract_web_entries
@ -306,8 +143,7 @@ class Export
# Extracts all credentials from a project, storing them in @creds
def extract_credential_entries
@creds = []
myworkspace.each_cred {|cred| @creds << cred}
@creds = Metasploit::Credential::Core.with_logins.with_public.with_private.workspace_id(myworkspace.id)
end
# Extracts all notes from a project, storing them in @notes
@ -346,14 +182,18 @@ class Export
end
end
def create_xml_element(key,value)
def create_xml_element(key,value,skip_encoding=false)
tag = key.gsub("_","-")
el = REXML::Element.new(tag)
if value
data = marshalize(value)
data.force_encoding(Encoding::BINARY) if data.respond_to?('force_encoding')
data.gsub!(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xFF])/n){ |x| "\\x%.2x" % x.unpack("C*")[0] }
el << REXML::Text.new(data)
unless skip_encoding
data = marshalize(value)
data.force_encoding(Encoding::BINARY) if data.respond_to?('force_encoding')
data.gsub!(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xFF])/n){ |x| "\\x%.2x" % x.unpack("C*")[0] }
el << REXML::Text.new(data)
else
el << value
end
end
return el
end
@ -572,22 +412,6 @@ class Export
end
report_file.write(" </vulns>\n")
# Credential sub-elements
report_file.write(" <creds>\n")
@creds.each do |cred|
next unless cred.service.host.id == host_id
report_file.write(" <cred>\n")
report_file.write(" #{create_xml_element("port",cred.service.port)}\n")
report_file.write(" #{create_xml_element("sname",cred.service.name)}\n")
cred.attributes.each_pair do |k,v|
next if k.strip =~ /id$/
el = create_xml_element(k,v)
report_file.write(" #{el}\n")
end
report_file.write(" </cred>\n")
end
report_file.write(" </creds>\n")
report_file.write(" </host>\n")
end
report_file.flush
@ -621,19 +445,6 @@ class Export
report_file.flush
end
# Extract credential data from @creds
def extract_credential_info(report_file)
@creds.each do |c|
report_file.write(" <credential>\n")
c.attributes.each_pair do |k,v|
cr = create_xml_element(k,v)
report_file.write(" #{cr}\n")
end
report_file.write(" </credential>\n")
report_file.write("\n")
end
report_file.flush
end
# Extract service data from @services
def extract_service_info(report_file)

View File

@ -22,6 +22,13 @@ class DBManager
include Msf::DBManager::Migration
include Msf::Framework::Offspring
#
# CONSTANTS
#
# The adapter to use to establish database connection.
ADAPTER = 'postgresql'
# Mainly, it's Ruby 1.9.1 that cause a lot of problems now, along with Ruby 1.8.6.
# Ruby 1.8.7 actually seems okay, but why tempt fate? Let's say 1.9.3 and beyond.
def warn_about_rubies
@ -34,16 +41,19 @@ class DBManager
# Returns true if we are ready to load/store data
def active
return false if not @usable
# We have established a connection, some connection is active, and we have run migrations
(ActiveRecord::Base.connected? && ActiveRecord::Base.connection_pool.connected? && migrated)# rescue false
# usable and migrated a just Boolean attributes, so check those first because they don't actually contact the
# database.
usable && migrated && connection_established?
end
# Returns true if the prerequisites have been installed
attr_accessor :usable
# Returns the list of usable database drivers
attr_accessor :drivers
def drivers
@drivers ||= []
end
attr_writer :drivers
# Returns the active driver
attr_accessor :driver
@ -86,9 +96,7 @@ class DBManager
# Database drivers can reset our KCODE, do not let them
$KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./
require "active_record"
initialize_metasploit_data_models
add_rails_engine_migration_paths
@usable = true
@ -98,22 +106,10 @@ class DBManager
return false
end
# Only include Mdm if we're not using Metasploit commercial versions
# If Mdm::Host is defined, the dynamically created classes
# are already in the object space
begin
unless defined? Mdm::Host
MetasploitDataModels.require_models
end
rescue NameError => e
warn_about_rubies
raise e
end
#
# Determine what drivers are available
#
initialize_drivers
initialize_adapter
#
# Instantiate the database sink
@ -123,53 +119,69 @@ class DBManager
true
end
# Checks if the spec passed to `ActiveRecord::Base.establish_connection` can connect to the database.
#
# @return [true] if an active connection can be made to the database using the current config.
# @return [false] if an active connection cannot be made to the database.
def connection_established?
begin
# use with_connection so the connection doesn't stay pinned to the thread.
ActiveRecord::Base.connection_pool.with_connection {
ActiveRecord::Base.connection.active?
}
rescue ActiveRecord::ConnectionNotEstablished, PG::ConnectionBad => error
elog("Connection not established: #{error.class} #{error}:\n#{error.backtrace.join("\n")}")
false
end
end
#
# Scan through available drivers
#
def initialize_drivers
self.drivers = []
tdrivers = %W{ postgresql }
tdrivers.each do |driver|
def initialize_adapter
ActiveRecord::Base.default_timezone = :utc
if connection_established? && ActiveRecord::Base.connection_config[:adapter] == ADAPTER
dlog("Already established connection to #{ADAPTER}, so reusing active connection.")
self.drivers << ADAPTER
self.driver = ADAPTER
else
begin
ActiveRecord::Base.default_timezone = :utc
ActiveRecord::Base.establish_connection(:adapter => driver)
if(self.respond_to?("driver_check_#{driver}"))
self.send("driver_check_#{driver}")
end
ActiveRecord::Base.establish_connection(adapter: ADAPTER)
ActiveRecord::Base.remove_connection
self.drivers << driver
rescue ::Exception
rescue Exception => error
@adapter_error = error
else
self.drivers << ADAPTER
self.driver = ADAPTER
end
end
if(not self.drivers.empty?)
self.driver = self.drivers[0]
end
# Database drivers can reset our KCODE, do not let them
$KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./
end
# Loads Metasploit Data Models and adds its migrations to migrations paths.
#
# @return [void]
def initialize_metasploit_data_models
# Provide access to ActiveRecord models shared w/ commercial versions
require "metasploit_data_models"
def add_rails_engine_migration_paths
unless defined? ActiveRecord
fail "Bundle installed '--without #{Bundler.settings.without.join(' ')}'. 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_data_model_migrations_pathname = MetasploitDataModels.root.join(
'db',
'migrate'
)
metasploit_data_model_migrations_path = metasploit_data_model_migrations_pathname.to_s
Rails.application.railties.engines.each do |engine|
migrations_paths = engine.paths['db/migrate'].existent_directories
# Since ActiveRecord::Migrator.migrations_paths can persist between
# instances of Msf::DBManager, such as in specs,
# metasploit_data_models_migrations_path may already be part of
# migrations_paths, in which case it should not be added or multiple
# migrations with the same version number errors will occur.
unless ActiveRecord::Migrator.migrations_paths.include? metasploit_data_model_migrations_path
ActiveRecord::Migrator.migrations_paths << metasploit_data_model_migrations_path
migrations_paths.each do |migrations_path|
# Since ActiveRecord::Migrator.migrations_paths can persist between
# instances of Msf::DBManager, such as in specs,
# migrations_path may already be part of
# migrations_paths, in which case it should not be added or multiple
# migrations with the same version number errors will occur.
unless ActiveRecord::Migrator.migrations_paths.include? migrations_path
ActiveRecord::Migrator.migrations_paths << migrations_path
end
end
end
end
@ -208,25 +220,22 @@ class DBManager
begin
self.migrated = false
create_db(nopts)
# Configure the database adapter
ActiveRecord::Base.establish_connection(nopts)
# Check ActiveRecord::Base was already connected by Rails::Application.initialize! or some other API.
unless connection_established?
create_db(nopts)
# Migrate the database, if needed
migrate
# Set the default workspace
framework.db.workspace = framework.db.default_workspace
# Flag that migration has completed
self.migrated = true
# Configure the database adapter
ActiveRecord::Base.establish_connection(nopts)
end
rescue ::Exception => e
self.error = e
elog("DB.connect threw an exception: #{e}")
dlog("Call stack: #{$@.join"\n"}", LEV_1)
return false
ensure
after_establish_connection
# Database drivers can reset our KCODE, do not let them
$KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./
end
@ -234,6 +243,29 @@ class DBManager
true
end
# Finishes {#connect} after `ActiveRecord::Base.establish_connection` has succeeded by {#migrate migrating database}
# and setting {#workspace}.
#
# @return [void]
def after_establish_connection
self.migrated = false
begin
# Migrate the database, if needed
migrate
# Set the default workspace
framework.db.workspace = framework.db.default_workspace
rescue ::Exception => exception
self.error = exception
elog("DB.connect threw an exception: #{exception}")
dlog("Call stack: #{exception.backtrace.join("\n")}", LEV_1)
else
# Flag that migration has completed
self.migrated = true
end
end
#
# Attempt to create the database
#
@ -259,7 +291,13 @@ class DBManager
errstr = e.to_s
if errstr =~ /does not exist/i or errstr =~ /Unknown database/
ilog("Database doesn't exist \"#{opts['database']}\", attempting to create it.")
ActiveRecord::Base.establish_connection(opts.merge('database' => nil))
ActiveRecord::Base.establish_connection(
opts.merge(
'database' => 'postgres',
'schema_search_path' => 'public'
)
)
ActiveRecord::Base.connection.create_database(opts['database'])
else
ilog("Trying to continue despite failed database creation: #{e}")

View File

@ -13,40 +13,40 @@ module Msf
# Elements that can be treated as text (i.e. do not need to be
# deserialized) in {#import_msf_web_page_element}
MSF_WEB_PAGE_TEXT_ELEMENT_NAMES = [
'auth',
'body',
'code',
'cookie',
'ctype',
'location',
'mtime'
'auth',
'body',
'code',
'cookie',
'ctype',
'location',
'mtime'
]
# Elements that can be treated as text (i.e. do not need to be
# deserialized) in {#import_msf_web_element}.
MSF_WEB_TEXT_ELEMENT_NAMES = [
'created-at',
'host',
'path',
'port',
'query',
'ssl',
'updated-at',
'vhost'
'created-at',
'host',
'path',
'port',
'query',
'ssl',
'updated-at',
'vhost'
]
# Elements that can be treated as text (i.e. do not need to be
# deserialized) in {#import_msf_web_vuln_element}.
MSF_WEB_VULN_TEXT_ELEMENT_NAMES = [
'blame',
'category',
'confidence',
'description',
'method',
'name',
'pname',
'proof',
'risk'
'blame',
'category',
'confidence',
'description',
'method',
'name',
'pname',
'proof',
'risk'
]
#
@ -80,8 +80,8 @@ module Msf
# FIXME https://www.pivotaltracker.com/story/show/46578647
# FIXME https://www.pivotaltracker.com/story/show/47128407
unserialized_params = unserialize_object(
element.elements['params'],
options[:allow_yaml]
element.elements['params'],
options[:allow_yaml]
)
info[:params] = nils_for_nulls(unserialized_params)
@ -127,8 +127,8 @@ module Msf
# FIXME https://www.pivotaltracker.com/story/show/46578647
# FIXME https://www.pivotaltracker.com/story/show/47128407
unserialized_headers = unserialize_object(
element.elements['headers'],
options[:allow_yaml]
element.elements['headers'],
options[:allow_yaml]
)
info[:headers] = nils_for_nulls(unserialized_headers)
@ -174,8 +174,8 @@ module Msf
# FIXME https://www.pivotaltracker.com/story/show/46578647
# FIXME https://www.pivotaltracker.com/story/show/47128407
unserialized_params = unserialize_object(
element.elements['params'],
options[:allow_yaml]
element.elements['params'],
options[:allow_yaml]
)
info[:params] = nils_for_nulls(unserialized_params)
@ -347,34 +347,40 @@ module Msf
end
end
host.elements.each('creds/cred') do |cred|
cred_data = {}
cred_data[:workspace] = wspace
cred_data[:host] = hobj
%W{port ptype sname proto proof active user pass}.each {|datum|
if cred.elements[datum].respond_to? :text
cred_data[datum.intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
## Handle old-style (pre 4.10) XML files
if btag == "MetasploitV4"
if host.elements['creds'].present?
unless host.elements['creds'].elements.empty?
origin = Metasploit::Credential::Origin::Import.create(filename: "console-import-#{Time.now.to_i}")
host.elements.each('creds/cred') do |cred|
username = cred.elements['user'].try(:text)
proto = cred.elements['proto'].try(:text)
sname = cred.elements['sname'].try(:text)
port = cred.elements['port'].try(:text)
# Handle blanks by resetting to sane default values
proto = "tcp" if proto.blank?
pass = cred.elements['pass'].try(:text)
pass = "" if pass == "*MASKED*"
private = create_credential_private(private_data: pass, private_type: :password)
public = create_credential_public(username: username)
core = create_credential_core(private: private, public: public, origin: origin, workspace_id: wspace.id)
create_credential_login(core: core,
workspace_id: wspace.id,
address: hobj.address,
port: port,
protocol: proto,
service_name: sname,
status: Metasploit::Model::Login::Status::UNTRIED)
end
end
}
%W{created-at updated-at}.each { |datum|
if cred.elements[datum].respond_to? :text
cred_data[datum.gsub("-","_")] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
end
}
%W{source-type source-id}.each { |datum|
if cred.elements[datum].respond_to? :text
cred_data[datum.gsub("-","_").intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
end
}
if cred_data[:pass] == "*MASKED*"
cred_data[:pass] = ""
cred_data[:active] = false
elsif cred_data[:pass] == "*BLANK PASSWORD*"
cred_data[:pass] = ""
end
report_cred(cred_data)
end
host.elements.each('sessions/session') do |sess|
sess_id = nils_for_nulls(sess.elements["id"].text.to_s.strip.to_i)
sess_data = {}
@ -399,9 +405,9 @@ module Msf
end
existing_session = get_session(
:workspace => sess_data[:host].workspace,
:addr => sess_data[:host].address,
:time => sess_data[:opened_at]
:workspace => sess_data[:host].workspace,
:addr => sess_data[:host].address,
:time => sess_data[:opened_at]
)
this_session = existing_session || report_session(sess_data)
next if existing_session
@ -423,6 +429,7 @@ module Msf
end
end
# Import web sites
doc.elements.each("/#{btag}/web_sites/web_site") do |web|
info = {}
@ -450,11 +457,11 @@ module Msf
%W{page form vuln}.each do |wtype|
doc.elements.each("/#{btag}/web_#{wtype}s/web_#{wtype}") do |element|
send(
"import_msf_web_#{wtype}_element",
element,
:allow_yaml => allow_yaml,
:workspace => wspace,
&block
"import_msf_web_#{wtype}_element",
element,
:allow_yaml => allow_yaml,
:workspace => wspace,
&block
)
end
end
@ -474,9 +481,9 @@ module Msf
# @raise [Msf::DBImportError] if unsupported format
def check_msf_xml_version!(document)
metadata = {
# FIXME https://www.pivotaltracker.com/story/show/47128407
:allow_yaml => false,
:root_tag => nil
# FIXME https://www.pivotaltracker.com/story/show/47128407
:allow_yaml => false,
:root_tag => nil
}
if document.elements['MetasploitExpressV1']
@ -493,6 +500,8 @@ module Msf
metadata[:root_tag] = 'MetasploitExpressV4'
elsif document.elements['MetasploitV4']
metadata[:root_tag] = 'MetasploitV4'
elsif document.elements['MetasploitV5']
metadata[:root_tag] = 'MetasploitV5'
end
unless metadata[:root_tag]
@ -580,3 +589,4 @@ module Msf
end
end
end

View File

@ -2,6 +2,7 @@
require 'rex/exploitation/cmdstager'
require 'msf/core/exploit/exe'
require 'msf/base/config'
module Msf

View File

@ -1,4 +1,5 @@
# -*- coding: binary -*-
require 'metasploit/framework/version'
require 'msf/core'
require 'msf/util'
@ -16,10 +17,10 @@ class Framework
# Versioning information
#
Major = 4
Minor = 9
Point = 3
Release = "-dev"
Major = Metasploit::Framework::Version::MAJOR
Minor = Metasploit::Framework::Version::MINOR
Point = Metasploit::Framework::Version::PATCH
Release = "-#{Metasploit::Framework::Version::PRERELEASE}"
if(Point)
Version = "#{Major}.#{Minor}.#{Point}#{Release}"
@ -41,14 +42,6 @@ class Framework
# EICAR canary
EICARCorrupted = ::Msf::Util::EXE.is_eicar_corrupted?
# API Version
APIMajor = 1
APIMinor = 0
# Base/API Version
VersionCore = Major + (Minor / 10.0)
VersionAPI = APIMajor + (APIMinor / 10.0)
#
# Mixin meant to be included into all classes that can have instances that
# should be tied to the framework, such as modules.

View File

@ -1,3 +1,6 @@
require 'metasploit/framework/api/version'
require 'metasploit/framework/core/version'
# Concern for behavior that all namespace modules that wrap Msf::Modules must support like version checking and
# grabbing the version specific-Metasploit* class.
module Msf::Modules::Namespace
@ -54,11 +57,11 @@ module Msf::Modules::Namespace
def version_compatible!(module_path, module_reference_name)
if const_defined?(:RequiredVersions)
required_versions = const_get(:RequiredVersions)
minimum_core_version = required_versions[0]
minimum_api_version = required_versions[1]
minimum_core_version = Gem::Version.new(required_versions[0].to_s)
minimum_api_version = Gem::Version.new(required_versions[1].to_s)
if (minimum_core_version > ::Msf::Framework::VersionCore or
minimum_api_version > ::Msf::Framework::VersionAPI)
if (minimum_core_version > Metasploit::Framework::Core::GEM_VERSION ||
minimum_api_version > Metasploit::Framework::API::GEM_VERSION)
raise Msf::Modules::VersionCompatibilityError.new(
:module_path => module_path,
:module_reference_name => module_reference_name,

View File

@ -46,4 +46,17 @@ class Msf::Post < Msf::Module
mod
end
# This method returns the ID of the {Mdm::Session} that the post module
# is currently running agaist.
#
# @return [NilClass] if there is no database record for the session
# @return [Fixnum] if there is a database record to get the id for
def session_db_id
if session.db_record
session.db_record.id
else
nil
end
end
end

View File

@ -4,6 +4,9 @@ module RPC
class RPC_Db < RPC_Base
private
include Metasploit::Credential::Creation
def db
self.framework.db.active
end
@ -15,6 +18,21 @@ private
self.framework.db.workspace
end
def fix_cred_options(opts)
new_opts = fix_options(opts)
# Convert some of are data back to symbols
if new_opts[:origin_type]
new_opts[:origin_type] = new_opts[:origin_type].to_sym
end
if new_opts[:private_type]
new_opts[:private_type] = new_opts[:private_type].to_sym
end
new_opts
end
def fix_options(opts)
newopts = {}
opts.each do |k,v|
@ -88,6 +106,40 @@ private
public
def rpc_create_cracked_credential(xopts)
opts = fix_cred_options(xopts)
create_credential(opts)
end
def rpc_create_credential(xopts)
opts = fix_cred_options(xopts)
core = create_credential(opts)
ret = {
username: core.public.try(:username),
private: core.private.try(:data),
private_type: core.private.try(:type),
realm_value: core.realm.try(:value),
realm_key: core.realm.try(:key)
}
if opts[:last_attempted_at] && opts[:status]
opts[:core] = core
opts[:last_attempted_at] = opts[:last_attempted_at].to_datetime
login = create_credential_login(opts)
ret[:host] = login.service.host.address,
ret[:sname] = login.service.name
ret[:status] = login.status
end
ret
end
def rpc_invalidate_login(xopts)
opts = fix_cred_options(xopts)
invalidate_login(opts)
end
def rpc_hosts(xopts)
::ActiveRecord::Base.connection_pool.with_connection {
opts, wspace = init_db_opts_workspace(xopts)
@ -490,33 +542,6 @@ public
}
end
def rpc_report_auth_info(xopts)
::ActiveRecord::Base.connection_pool.with_connection {
opts, wspace = init_db_opts_workspace(xopts)
res = self.framework.db.report_auth_info(opts)
return { :result => 'success' } if(res)
{ :result => 'failed' }
}
end
def rpc_get_auth_info(xopts)
::ActiveRecord::Base.connection_pool.with_connection {
opts, wspace = init_db_opts_workspace(xopts)
ret = {}
ret[:auth_info] = []
# XXX: This method doesn't exist...
ai = self.framework.db.get_auth_info(opts)
ai.each do |i|
info = {}
i.each do |k,v|
info[k.to_sym] = v
end
ret[:auth_info] << info
end
ret
}
end
def rpc_get_ref(name)
::ActiveRecord::Base.connection_pool.with_connection {
db_check
@ -828,42 +853,6 @@ public
}
end
# requires host, port, user, pass, ptype, and active
def rpc_report_cred(xopts)
::ActiveRecord::Base.connection_pool.with_connection {
opts, wspace = init_db_opts_workspace(xopts)
res = framework.db.find_or_create_cred(opts)
return { :result => 'success' } if res
{ :result => 'failed' }
}
end
#right now workspace is the only option supported
def rpc_creds(xopts)
::ActiveRecord::Base.connection_pool.with_connection {
opts, wspace = init_db_opts_workspace(xopts)
limit = opts.delete(:limit) || 100
offset = opts.delete(:offset) || 0
ret = {}
ret[:creds] = []
::Mdm::Cred.find(:all, :include => {:service => :host}, :conditions => ["hosts.workspace_id = ?",
framework.db.workspace.id ], :limit => limit, :offset => offset).each do |c|
cred = {}
cred[:host] = c.service.host.address if(c.service.host)
cred[:updated_at] = c.updated_at.to_i
cred[:port] = c.service.port
cred[:proto] = c.service.proto
cred[:sname] = c.service.name
cred[:type] = c.ptype
cred[:user] = c.user
cred[:pass] = c.pass
cred[:active] = c.active
ret[:creds] << cred
end
ret
}
end
def rpc_import_data(xopts)
::ActiveRecord::Base.connection_pool.with_connection {
@ -1046,7 +1035,7 @@ public
end
cdb = ""
if ::ActiveRecord::Base.connected?
if framework.db.connection_established?
::ActiveRecord::Base.connection_pool.with_connection { |conn|
if conn.respond_to? :current_database
cdb = conn.current_database

View File

@ -99,7 +99,12 @@ class ThreadManager < Array
begin
argv.shift.call(*argv)
rescue ::Exception => e
elog("thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} error:#{e.class} #{e} source:#{::Thread.current[:tm_call].inspect}")
elog(
"thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} " \
"error: #{e.class} #{e}\n" \
" source:\n" \
" #{::Thread.current[:tm_call].join "\n "}"
)
elog("Call Stack\n#{e.backtrace.join("\n")}")
raise e
ensure

View File

@ -33,18 +33,6 @@ if (RUBY_VERSION =~ /^1\.9\.1/)
$stderr.puts "*** Ruby 1.9.1 is not supported, please upgrade to Ruby 1.9.3 or newer."
end
if(RUBY_VERSION =~ /^(1\.9|2\.0)\./)
# Load rubygems before changing default_internal, otherwise we may get
# Encoding::UndefinedConversionError as the gemspec files are loaded
require 'rubygems'
Gem::Version # trigger Rubygems to fully load
# Force binary encoding for Ruby versions that support it
if(Object.const_defined?('Encoding') and Encoding.respond_to?('default_external='))
Encoding.default_external = Encoding.default_internal = "binary"
end
end
if(RUBY_PLATFORM == 'java')
require 'socket'
s = Socket.new(::Socket::AF_INET, ::Socket::SOCK_STREAM, ::Socket::IPPROTO_TCP)

View File

@ -408,7 +408,7 @@ class Core
avdwarn = nil
banner_trailers = {
:version => "%yelmetasploit v#{Msf::Framework::Version} [core:#{Msf::Framework::VersionCore} api:#{Msf::Framework::VersionAPI}]%clr",
:version => "%yelmetasploit v#{Msf::Framework::Version} [core:#{Metasploit::Framework::Core::GEM_VERSION} api:#{Metasploit::Framework::API::GEM_VERSION}]%clr",
:exp_aux_pos => "#{framework.stats.num_exploits} exploits - #{framework.stats.num_auxiliary} auxiliary - #{framework.stats.num_post} post",
:pay_enc_nop => "#{framework.stats.num_payloads} payloads - #{framework.stats.num_encoders} encoders - #{framework.stats.num_nops} nops",
:free_trial => "Free Metasploit Pro trial: http://r-7.co/trymsp",

View File

@ -18,6 +18,8 @@ class Db
# TODO: Not thrilled about including this entire module for just store_local.
include Msf::Auxiliary::Report
include Metasploit::Credential::Creation
#
# The dispatcher's name.
#
@ -217,7 +219,6 @@ class Db
return unless active?
::ActiveRecord::Base.connection_pool.with_connection {
onlyup = false
host_search = nil
set_rhosts = false
mode = :search
delete_count = 0
@ -580,7 +581,6 @@ class Db
return
end
mode = :search
while (arg = args.shift)
case arg
#when "-a","--add"
@ -648,73 +648,125 @@ class Db
end
def cmd_creds_help
print_line "Usage: creds [addr range]"
print_line "Usage: creds -a <addr range> -p <port> -t <type> -u <user> -P <pass>"
print_line
print_line " -a,--add Add creds to the given addresses instead of listing"
print_line " -d,--delete Delete the creds instead of searching"
print_line " -h,--help Show this help information"
print_line " -o <file> Send output to a file in csv format"
print_line " -p,--port <portspec> List creds matching this port spec"
print_line " -s <svc names> List creds matching these service names"
print_line " -t,--type <type> Add a cred of this type (only with -a). Default: password"
print_line " -u,--user Add a cred for this user (only with -a). Default: blank"
print_line " -P,--password Add a cred with this password (only with -a). Default: blank"
print_line " -R,--rhosts Set RHOSTS from the results of the search"
print_line " -S,--search Search string to filter by"
print_line " -c,--columns Columns of interest"
print_line "With no sub-command, list credentials. If an address range is"
print_line "given, show only credentials with logins on hosts within that"
print_line "range."
print_line
print_line "Examples:"
print_line " creds # Default, returns all active credentials"
print_line " creds all # Returns all credentials active or not"
print_line "Usage - Listing credentials:"
print_line " creds [filter options] [address range]"
print_line
print_line "Usage - Adding credentials:"
print_line " creds add-ntlm <user> <ntlm hash> [domain]"
print_line " creds add-password <user> <password> [realm] [realm-type]"
print_line " creds add-ssh-key <user> </path/to/id_rsa> [realm-type]"
print_line "Where [realm type] can be one of:"
Metasploit::Model::Realm::Key::SHORT_NAMES.each do |short, description|
print_line " #{short} - #{description}"
end
print_line
print_line "General options"
print_line " -h,--help Show this help information"
print_line
print_line "Filter options for listing"
print_line " -P,--password <regex> List passwords that match this regex"
print_line " -p,--port <portspec> List creds with logins on services matching this port spec"
print_line " -s <svc names> List creds matching comma-separated service names"
print_line " -u,--user <regex> List users that match this regex"
print_line
print_line "Examples, listing:"
print_line " creds # Default, returns all credentials"
print_line " creds 1.2.3.4/24 # nmap host specification"
print_line " creds -p 22-25,445 # nmap port specification"
print_line " creds 10.1.*.* -s ssh,smb all"
print_line " creds -s ssh,smb # All creds associated with a login on SSH or SMB services"
print_line
print_line
print_line "Examples, adding:"
print_line " # Add a user with an NTLMHash"
print_line " creds add-ntlm alice 5cfe4c82d9ab8c66590f5b47cd6690f1:978a2e2e1dec9804c6b936f254727f9a"
print_line " # Add a user with a blank password and a domain"
print_line " creds add-password bob '' contosso"
print_line " # Add a user with an SSH key"
print_line " creds add-ssh-key root /root/.ssh/id_rsa"
print_line
end
#
# Can return return active or all, on a certain host or range, on a
# certain port or range, and/or on a service name.
#
def cmd_creds(*args)
return unless active?
::ActiveRecord::Base.connection_pool.with_connection {
search_param = nil
inactive_ok = false
type = "password"
set_rhosts = false
output_file = nil
host_ranges = []
port_ranges = []
rhosts = []
svcs = []
delete_count = 0
search_term = nil
cred_table_columns = [ 'host', 'port', 'user', 'pass', 'type', 'proof', 'active?' ]
user = nil
# Short-circuit help
if args.delete "-h"
cmd_creds_help
return
# @param private_type [Symbol] See `Metasploit::Credential::Creation#create_credential`
# @param username [String]
# @param password [String]
# @param realm [String]
# @param realm_type [String] A key in `Metasploit::Model::Realm::Key::SHORT_NAMES`
def creds_add(private_type, username, password=nil, realm=nil, realm_type=nil)
cred_data = {
username: username,
private_data: password,
private_type: private_type,
workspace_id: framework.db.workspace,
origin_type: :import,
filename: "msfconsole"
}
if realm.present?
if realm_type.present?
realm_key = Metasploit::Model::Realm::Key::SHORT_NAMES[realm_type]
if realm_key.nil?
valid = Metasploit::Model::Realm::Key::SHORT_NAMES.keys.map{|n|"'#{n}'"}.join(", ")
print_error("Invalid realm type: #{realm_type}. Valid values: #{valid}")
return
end
end
realm_key ||= Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
cred_data.merge!(
realm_value: realm,
realm_key: realm_key
)
end
mode = :search
begin
create_credential(cred_data)
rescue ActiveRecord::RecordInvalid => e
print_error("Failed to add #{private_type}: #{e}")
end
end
def creds_add_non_replayable_hash(*args)
creds_add(:non_replayable_hash, *args)
end
def creds_add_ntlm_hash(*args)
creds_add(:ntlm_hash, *args)
end
def creds_add_password(*args)
creds_add(:password, *args)
end
def creds_add_ssh_key(username, *args)
key_file, realm = args
begin
key_data = File.read(key_file)
rescue ::Errno::EACCES, ::Errno::ENOENT => e
print_error("Failed to add ssh key: #{e}")
else
creds_add(:ssh_key, username, key_data, realm)
end
end
def creds_search(*args)
host_ranges = []
port_ranges = []
svcs = []
#cred_table_columns = [ 'host', 'port', 'user', 'pass', 'type', 'proof', 'active?' ]
cred_table_columns = [ 'host', 'service', 'public', 'private', 'realm', 'private_type' ]
user = nil
delete_count = 0
while (arg = args.shift)
case arg
when "-a","--add"
mode = :add
when "-d"
mode = :delete
when "-h"
cmd_creds_help
return
when '-o'
output_file = args.shift
if (!output_file)
@ -745,32 +797,14 @@ class Db
print_error("Argument required for -P")
return
end
when "-R"
set_rhosts = true
when '-S', '--search'
search_term = /#{args.shift}/nmi
when "-u","--user"
user = args.shift
if (!user)
print_error("Argument required for -u")
return
end
when '-c','--columns'
columns = args.shift
unless columns
print_error("Argument required for -c, you may use any of #{cred_table_columns.join(',')}")
return
end
cred_table_columns = columns.split(/[\s]*,[\s]*/).select do |col|
cred_table_columns.include?(col)
end
if cred_table_columns.empty?
print_error("Argument -c requires valid columns")
return
end
when "all"
# The user wants inactive passwords, too
inactive_ok = true
when "-d"
mode = :delete
else
# Anything that wasn't an option is a host to search for
unless (arg_host_range(arg, host_ranges))
@ -779,33 +813,13 @@ class Db
end
end
if mode == :add
if port_ranges.length != 1 or port_ranges.first.length != 1
print_error("Exactly one port required")
return
end
port = port_ranges.first.first
host_ranges.each do |range|
range.each do |host|
cred = framework.db.find_or_create_cred(
:host => host,
:port => port,
:user => (user == "NULL" ? nil : user),
:pass => (pass == "NULL" ? nil : pass),
:type => ptype,
:sname => service,
:active => true
)
print_status("Time: #{cred.updated_at} Credential: host=#{cred.service.host.address} port=#{cred.service.port} proto=#{cred.service.proto} sname=#{cred.service.name} type=#{cred.ptype} user=#{cred.user} pass=#{cred.pass} active=#{cred.active}")
end
end
return
end
# If we get here, we're searching. Delete implies search
if user
user_regex = Regexp.compile(user)
end
if pass
pass_regex = Regexp.compile(pass)
end
# normalize
ports = port_ranges.flatten.uniq
@ -815,91 +829,130 @@ class Db
'Columns' => cred_table_columns
}
tbl_opts.merge!(
'ColProps' => {
'pass' => { 'MaxChar' => 64 },
'proof' => { 'MaxChar' => 56 }
}
) if search_term.nil?
tbl = Rex::Ui::Text::Table.new(tbl_opts)
creds_returned = 0
inactive_count = 0
# Now do the actual search
framework.db.each_cred(framework.db.workspace) do |cred|
# skip if it's inactive and user didn't ask for all
if !cred.active && !inactive_ok
inactive_count += 1
next
end
::ActiveRecord::Base.connection_pool.with_connection {
query = Metasploit::Credential::Core.where(
workspace_id: framework.db.workspace,
)
if search_term
next unless cred.attribute_names.any? { |a| cred[a.intern].to_s.match(search_term) }
end
# Also skip if the user is searching for something and this
# one doesn't match
includes = false
host_ranges.map do |rw|
includes = rw.include? cred.service.host.address
break if includes
end
next unless host_ranges.empty? or includes
query.each do |core|
# Same for ports
next unless ports.empty? or ports.include? cred.service.port
# Exclude creds that don't match the given user
if user_regex.present? && !core.public.username.match(user_regex)
next
end
# Same for service names
next unless svcs.empty? or svcs.include?(cred.service.name)
# Exclude creds that don't match the given pass
if pass_regex.present? && !core.private.data.match(pass_regex)
next
end
if user_regex
next unless user_regex.match(cred.user)
end
if core.logins.empty?
# Skip cores that don't have any logins if the user specified a
# filter based on host, port, or service name
next if host_ranges.any? || ports.any? || svcs.any?
row = cred_table_columns.map do |col|
case col
when 'host'
cred.service.host.address
when 'port'
cred.service.port
when 'type'
cred.ptype
tbl << [
"", # host
"", # port
core.public,
core.private,
core.realm,
core.private ? core.private.class.model_name.human : "",
]
else
cred.send(col.intern)
core.logins.each do |login|
if svcs.present? && !svcs.include?(login.service.name)
next
end
if ports.present? && !ports.include?(login.service.port)
next
end
# If none of this Core's associated Logins is for a host within
# the user-supplied RangeWalker, then we don't have any reason to
# print it out. However, we treat the absence of ranges as meaning
# all hosts.
if host_ranges.present? && !host_ranges.any? { |range| range.include?(login.service.host.address) }
next
end
row = [ login.service.host.address ]
if login.service.name.present?
row << "#{login.service.port}/#{login.service.proto} (#{login.service.name})"
else
row << "#{login.service.port}/#{login.service.proto}"
end
row += [
core.public,
core.private,
core.realm,
core.private ? core.private.class.model_name.human : "",
]
tbl << row
end
end
if mode == :delete
core.destroy
delete_count += 1
end
end
tbl << row
if mode == :delete
cred.destroy
delete_count += 1
end
if set_rhosts
addr = (cred.service.host.scope ? cred.service.host.address + '%' + cred.service.host.scope : cred.service.host.address )
rhosts << addr
end
creds_returned += 1
end
print_line
if output_file.nil?
print_line(tbl.to_s)
if !inactive_ok && inactive_count > 0
# Then we're not printing the inactive ones. Let the user know
# that there are some they are not seeing and how to get at
# them.
print_line "Also found #{inactive_count} inactive creds (`creds all` to list them)"
print_line
end
else
# create the output file
::File.open(output_file, "wb") { |f| f.write(tbl.to_csv) }
print_status("Wrote services to #{output_file}")
print_status("Deleted #{delete_count} creds") if delete_count > 0
}
end
#
# Can return return active or all, on a certain host or range, on a
# certain port or range, and/or on a service name.
#
def cmd_creds(*args)
return unless active?
# Short-circuit help
if args.delete "-h"
cmd_creds_help
return
end
set_rhosts_from_addrs(rhosts.uniq) if set_rhosts
subcommand = args.shift
case subcommand
when "add-ntlm"
creds_add_ntlm_hash(*args)
when "add-password"
creds_add_password(*args)
when "add-hash"
creds_add_non_replayable_hash(*args)
when "add-ssh-key"
creds_add_ssh_key(*args)
else
# then it's not actually a subcommand
args.unshift(subcommand) if subcommand
creds_search(*args)
end
print_status("Deleted #{delete_count} credentials") if delete_count > 0
}
end
def cmd_creds_tabs(str, words)
case words.length
when 1
# subcommands
tabs = [ 'add-ntlm', 'add-password', 'add-hash', 'add-ssh-key', ]
when 2
tabs = if words[1] == 'add-ssh-key'
tab_complete_filenames(str, words)
else
[]
end
#when 5
# tabs = Metasploit::Model::Realm::Key::SHORT_NAMES.keys
else
tabs = []
end
return tabs
end
def cmd_notes_help
@ -1139,8 +1192,8 @@ class Db
info = args.shift
if(!info)
print_error("Can't make loot with no info")
return
end
return
end
when '-t'
typelist = args.shift
if(!typelist)
@ -1188,8 +1241,8 @@ class Db
range.each do |host|
file = File.open(filename, "rb")
contents = file.read
lootfile = framework.db.find_or_create_loot(:type => type, :host => host,:info => info, :data => contents,:path => filename,:name => name)
print_status("Added loot #{host}")
lootfile = framework.db.find_or_create_loot(:type => type, :host => host, :info => info, :data => contents, :path => filename, :name => name)
print_status("Added loot for #{host} (#{lootfile})")
end
end
return
@ -1345,7 +1398,7 @@ class Db
def cmd_db_import(*args)
return unless active?
::ActiveRecord::Base.connection_pool.with_connection {
if (args.include?("-h") or not (args and args.length > 0))
if args.include?("-h") || ! (args && args.length > 0)
cmd_db_import_help
return
end
@ -1408,8 +1461,8 @@ class Db
next
rescue REXML::ParseException => e
print_error("Failed to import #{filename} due to malformed XML:")
print_error("#{$!.class}: #{$!}")
elog("Failed to import #{filename}: #{$!.class}: #{$!}")
print_error("#{e.class}: #{e}")
elog("Failed to import #{filename}: #{e.class}: #{e}")
dlog("Call stack: #{$@.join("\n")}", LEV_3)
next
end
@ -1577,7 +1630,8 @@ class Db
#
def cmd_db_status(*args)
return if not db_check_driver
if ::ActiveRecord::Base.connected?
if framework.db.connection_established?
cdb = ""
::ActiveRecord::Base.connection_pool.with_connection { |conn|
if conn.respond_to? :current_database
@ -1710,7 +1764,6 @@ class Db
end
def db_find_tools(tools)
found = true
missed = []
tools.each do |name|
if(! Rex::FileUtils.find_full_path(name))

View File

@ -179,50 +179,60 @@ class Driver < Msf::Ui::Driver
end
end
# Look for our database configuration in the following places, in order:
# command line arguments
# environment variable
# configuration directory (usually ~/.msf3)
dbfile = opts['DatabaseYAML']
dbfile ||= ENV["MSF_DATABASE_CONFIG"]
dbfile ||= File.join(Msf::Config.get_config_root, "database.yml")
if (dbfile and File.exists? dbfile)
if File.readable?(dbfile)
dbinfo = YAML.load(File.read(dbfile))
dbenv = opts['DatabaseEnv'] || "production"
db = dbinfo[dbenv]
else
print_error("Warning, #{dbfile} is not readable. Try running as root or chmod.")
end
if not db
print_error("No database definition for environment #{dbenv}")
else
if not framework.db.connect(db)
if framework.db.error.to_s =~ /RubyGem version.*pg.*0\.11/i
print_error("***")
print_error("*")
print_error("* Metasploit now requires version 0.11 or higher of the 'pg' gem for database support")
print_error("* There a three ways to accomplish this upgrade:")
print_error("* 1. If you run Metasploit with your system ruby, simply upgrade the gem:")
print_error("* $ rvmsudo gem install pg ")
print_error("* 2. Use the Community Edition web interface to apply a Software Update")
print_error("* 3. Uninstall, download the latest version, and reinstall Metasploit")
print_error("*")
print_error("***")
print_error("")
print_error("")
end
if framework.db.connection_established?
framework.db.after_establish_connection
else
# Look for our database configuration in the following places, in order:
# command line arguments
# environment variable
# configuration directory (usually ~/.msf3)
dbfile = opts['DatabaseYAML']
dbfile ||= ENV["MSF_DATABASE_CONFIG"]
dbfile ||= File.join(Msf::Config.get_config_root, "database.yml")
print_error("Failed to connect to the database: #{framework.db.error}")
if (dbfile and File.exists? dbfile)
if File.readable?(dbfile)
dbinfo = YAML.load(File.read(dbfile))
dbenv = opts['DatabaseEnv'] || Rails.env
db = dbinfo[dbenv]
else
self.framework.modules.refresh_cache_from_database
print_error("Warning, #{dbfile} is not readable. Try running as root or chmod.")
end
if self.framework.modules.cache_empty?
print_status("The initial module cache will be built in the background, this can take 2-5 minutes...")
end
if not db
print_error("No database definition for environment #{dbenv}")
else
framework.db.connect(db)
end
end
end
# framework.db.active will be true if after_establish_connection ran directly when connection_established? was
# already true or if framework.db.connect called after_establish_connection.
if framework.db.active
self.framework.modules.refresh_cache_from_database
if self.framework.modules.cache_empty?
print_status("The initial module cache will be built in the background, this can take 2-5 minutes...")
end
elsif !framework.db.error.nil?
if framework.db.error.to_s =~ /RubyGem version.*pg.*0\.11/i
print_error("***")
print_error("*")
print_error("* Metasploit now requires version 0.11 or higher of the 'pg' gem for database support")
print_error("* There a three ways to accomplish this upgrade:")
print_error("* 1. If you run Metasploit with your system ruby, simply upgrade the gem:")
print_error("* $ rvmsudo gem install pg ")
print_error("* 2. Use the Community Edition web interface to apply a Software Update")
print_error("* 3. Uninstall, download the latest version, and reinstall Metasploit")
print_error("*")
print_error("***")
print_error("")
print_error("")
end
print_error("Failed to connect to the database: #{framework.db.error}")
end
end
# Initialize the module paths only if we didn't get passed a Framework instance

View File

@ -2,31 +2,18 @@
# Use bundler to load dependencies
#
GEMFILE_EXTENSIONS = [
'.local',
''
]
# Override the normal rails default, so that msfconsole will come up in production mode instead of development mode
# unless the `--environment` flag is passed.
ENV['RAILS_ENV'] ||= 'production'
unless ENV['BUNDLE_GEMFILE']
require 'pathname'
require 'pathname'
root = Pathname.new(__FILE__).expand_path.parent.parent
config = root.join('config')
require config.join('boot')
msfenv_real_pathname = Pathname.new(__FILE__).realpath
root = msfenv_real_pathname.parent.parent
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/setup'
rescue ::LoadError
$stderr.puts "[*] Metasploit requires the Bundler gem to be installed"
$stderr.puts " $ gem install bundler"
exit(0)
# Requiring environment will define the Metasploit::Framework::Application as the one and only Rails::Application in
# this process and cause an error if a Rails.application is already defined, such as when loading msfenv through
# msfconsole in Metasploit Pro.
unless defined?(Rails) && !Rails.application.nil?
require config.join('environment')
end

View File

@ -35,9 +35,9 @@ module Net; module SSH
# appropriately. The new key is returned. If the key itself is
# encrypted (requiring a passphrase to use), the user will be
# prompted to enter their password unless passphrase works.
def load_private_key(filename, passphrase=nil)
def load_private_key(filename, passphrase=nil, ask_passphrase=true)
data = File.open(File.expand_path(filename), "rb") {|f| f.read(f.stat.size)}
load_data_private_key(data, passphrase, filename)
load_data_private_key(data, passphrase, ask_passphrase, filename)
end
# Loads a private key. It will correctly determine
@ -45,7 +45,7 @@ module Net; module SSH
# appropriately. The new key is returned. If the key itself is
# encrypted (requiring a passphrase to use), the user will be
# prompted to enter their password unless passphrase works.
def load_data_private_key(data, passphrase=nil, filename="")
def load_data_private_key(data, passphrase=nil, ask_passphrase= true, filename="")
if data.match(/-----BEGIN DSA PRIVATE KEY-----/)
key_type = OpenSSL::PKey::DSA
elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/)
@ -62,7 +62,7 @@ module Net; module SSH
begin
return key_type.new(data, passphrase || 'invalid')
rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError => e
if encrypted_key
if encrypted_key && ask_passphrase
tries += 1
if tries <= 3
passphrase = prompt("Enter passphrase for #{filename}:", false)

View File

@ -739,11 +739,15 @@ class Error < ::RuntimeError
# returns an error string if it exists, otherwise just the error code
def get_error(error)
string = ''
if @@errors[error]
if error && @@errors[error]
string = @@errors[error]
else
elsif error
string = sprintf('0x%.8x',error)
else
string = "Unknown error"
end
string
end
end
@ -781,6 +785,10 @@ class InvalidPacket < Error
attr_accessor :word_count
attr_accessor :command
attr_accessor :error_code
def error_name
get_error(error_code)
end
end
class InvalidWordCount < InvalidPacket
@ -807,7 +815,7 @@ end
class ErrorCode < InvalidPacket
def to_s
'The server responded with error: ' +
self.get_error(self.error_code) +
self.error_name +
" (Command=#{self.command} WordCount=#{self.word_count})"
end
end

View File

@ -275,9 +275,9 @@ protected
nameline << pad(' ', last_col, last_idx)
remainder = colprops[last_idx]['MaxWidth'] - last_col.length
if (remainder < 0)
remainder = 0
end
if (remainder < 0)
remainder = 0
end
barline << (' ' * (cellpad + remainder))
end
nameline << col
@ -305,7 +305,7 @@ protected
last_cell = nil
last_idx = nil
row.each_with_index { |cell, idx|
if (last_cell)
if (idx != 0)
line << pad(' ', last_cell.to_s, last_idx)
end
# line << pad(' ', cell.to_s, idx)

View File

@ -1,73 +0,0 @@
load 'active_record/railties/databases.rake'
require 'metasploit/framework'
require 'metasploit/framework/database'
# A modification to remove dependency on Rails.env
#
# @see https://github.com/rails/rails/blob/ddce29bfa12462fde2342a0c2bd0eefd420c0eab/activerecord/lib/active_record/railties/databases.rake#L550
def configs_for_environment
environments = [Metasploit::Framework.env]
if Metasploit::Framework.env.development?
environments << 'test'
end
environment_configurations = ActiveRecord::Base.configurations.values_at(*environments)
present_environment_configurations = environment_configurations.compact
valid_environment_configurations = present_environment_configurations.reject { |config|
config['database'].blank?
}
valid_environment_configurations
end
# emulate initializer "active_record.initialize_database" from active_record/railtie
ActiveSupport.on_load(:active_record) do
self.configurations = Metasploit::Framework::Database.configurations
puts "Connecting to database specified by #{Metasploit::Framework::Database.configurations_pathname}"
spec = configurations[Metasploit::Framework.env]
establish_connection(spec)
end
#
# Remove tasks that aren't supported
#
Rake::TaskManager.class_eval do
def remove_task(task_name)
@tasks.delete(task_name.to_s)
end
end
Rake.application.remove_task('db:fixtures:load')
# completely replace db:load_config and db:seed as they will attempt to use
# Rails.application, which does not exist
Rake::Task['db:load_config'].clear
Rake::Task['db:seed'].clear
db_namespace = namespace :db do
task :load_config do
ActiveRecord::Base.configurations = Metasploit::Framework::Database.configurations
ActiveRecord::Migrator.migrations_paths = [
# rails isn't in Gemfile, so can't use the more appropriate
# Metasploit::Engine.instance.paths['db/migrate'].to_a since using
# Metasploit::Engine requires rails.
MetasploitDataModels.root.join('db', 'migrate').to_s
]
end
desc 'Load the seed data from db/seeds.rb'
task :seed do
db_namespace['abort_if_pending_migrations'].invoke
seeds_pathname = Metasploit::Framework.root.join('db', 'seeds.rb')
if seeds_pathname.exist?
load(seeds_pathname)
end
end
end

7
lib/tasks/databases.rake Normal file
View File

@ -0,0 +1,7 @@
namespace :db do
# Add onto the task so that after adding Rails.application.paths['db/migrate']
task :load_config do
# It's important to call to_a or the paths will just be relative and not realpaths
ActiveRecord::Migrator.migrations_paths += Metasploit::Credential::Engine.instance.paths['db/migrate'].to_a
end
end

View File

@ -1,21 +0,0 @@
# Rake tasks added for compatibility with rake tasks that depend on a Rails
# environment, such as those in activerecord
# Would normally load config/environment.rb of the rails application.
#
# @see https://github.com/rails/rails/blob/e2908356672d4459ada0064f773efd820efda822/railties/lib/rails/application.rb#L190
task :environment do
# ensures that Mdm models are available for migrations which use the models
MetasploitDataModels.require_models
# avoids the need for Rails.root in db:schema:dump
schema_pathname = Metasploit::Framework.root.join('db', 'schema.rb')
ENV['SCHEMA'] = schema_pathname.to_s
end
# This would normally default RAILS_ENV to development if ENV['RAILS_ENV'] is
# not set
#
# @see https://github.com/rails/rails/blob/1a275730b290c1f06d4e8df680d22ae1b41ab585/railties/lib/rails/tasks/misc.rake#L3
task :rails_env do
end

View File

@ -1 +0,0 @@
require 'zip/zip'

File diff suppressed because it is too large Load Diff

View File

@ -1,162 +0,0 @@
= Version 0.9.4
Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now
allows comment, extra field and compression method to be specified.
= Version 0.9.3
Fixed: Added ZipEntry::name_encoding which retrieves the character
encoding of the name and comment of the entry. Also added convenience
methods ZipEntry::name_in(enc) and ZipEntry::comment_in(enc) for
getting zip entry names and comments in a specified character
encoding.
= Version 0.9.2
Fixed: Renaming an entry failed if the entry's new name was a
different length than its old name. (Diego Barros)
= Version 0.9.1
Added symlink support and support for unix file permissions. Reduced
memory usage during decompression.
New methods ZipFile::[follow_symlinks, restore_times, restore_permissions, restore_ownership].
New methods ZipEntry::unix_perms, ZipInputStream::eof?.
Added documentation and test for new ZipFile::extract.
Added some of the API suggestions from sf.net #1281314.
Applied patch for sf.net bug #1446926.
Applied patch for sf.net bug #1459902.
Rework ZipEntry and delegate classes.
= Version 0.5.12
Fixed problem with writing binary content to a ZipFile in MS Windows.
= Version 0.5.11
Fixed name clash file method copy_stream from fileutils.rb. Fixed
problem with references to constant CHUNK_SIZE.
ZipInputStream/AbstractInputStream read is now buffered like ruby IO's
read method, which means that read and gets etc can be mixed. The
unbuffered read method has been renamed to sysread.
= Version 0.5.10
Fixed method name resolution problem with FileUtils::copy_stream and
IOExtras::copy_stream.
= Version 0.5.9
Fixed serious memory consumption issue
= Version 0.5.8
Fixed install script.
= Version 0.5.7
install.rb no longer assumes it is being run from the toplevel source
dir. Directory structure changed to reflect common ruby library
project structure. Migrated from RubyUnit to Test::Unit format. Now
uses Rake to build source packages and gems and run unit tests.
= Version 0.5.6
Fix for FreeBSD 4.9 which returns Errno::EFBIG instead of
Errno::EINVAL for some invalid seeks. Fixed 'version needed to
extract'-field incorrect in local headers.
= Version 0.5.5
Fix for a problem with writing zip files that concerns only ruby 1.8.1.
= Version 0.5.4
Significantly reduced memory footprint when modifying zip files.
= Version 0.5.3
Added optimization to avoid decompressing and recompressing individual
entries when modifying a zip archive.
= Version 0.5.2
Fixed ZipFile corruption bug in ZipFile class. Added basic unix
extra-field support.
= Version 0.5.1
Fixed ZipFile.get_output_stream bug.
= Version 0.5.0
List of changes:
* Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility
* Changed method names from camelCase to rubys underscore style.
* Installs to zip/ subdir instead of directly to site_ruby
* Added ZipFile.directory and ZipFile.file - each method return an
object that can be used like Dir and File only for the contents of the
zip file.
* Added sample application zipfind which works like Find.find, only
Zip::ZipFind.find traverses into zip archives too.
Bug fixes:
* AbstractInputStream.each_line with non-default separator
= Version 0.5.0a
Source reorganized. Added ziprequire, which can be used to load ruby
modules from a zip file, in a fashion similar to jar files in
Java. Added gtkRubyzip, another sample application. Implemented
ZipInputStream.lineno and ZipInputStream.rewind
Bug fixes:
* Read and write date and time information correctly for zip entries.
* Fixed read() using separate buffer, causing mix of gets/readline/read to
cause problems.
= Version 0.4.2
Performance optimizations. Test suite runs in half the time.
= Version 0.4.1
Windows compatibility fixes.
= Version 0.4.0
Zip::ZipFile is now mutable and provides a more convenient way of
modifying zip archives than Zip::ZipOutputStream. Operations for
adding, extracting, renaming, replacing and removing entries to zip
archives are now available.
Runs without warnings with -w switch.
Install script install.rb added.
= Version 0.3.1
Rudimentary support for writing zip archives.
= Version 0.2.2
Fixed and extended unit test suite. Updated to work with ruby/zlib
0.5. It doesn't work with earlier versions of ruby/zlib.
= Version 0.2.0
Class ZipFile added. Where ZipInputStream is used to read the
individual entries in a zip file, ZipFile reads the central directory
in the zip archive, so you can get to any entry in the zip archive
without having to skipping through all the preceeding entries.
= Version 0.1.0
First working version of ZipInputStream.

View File

@ -1,72 +0,0 @@
= rubyzip
rubyzip is a ruby library for reading and writing zip files.
= Install
If you have rubygems you can install rubyzip directly from the gem
repository
gem install rubyzip
Otherwise obtain the source (see below) and run
ruby install.rb
To run the unit tests you need to have test::unit installed
rake test
= Documentation
There is more than one way to access or create a zip archive with
rubyzip. The basic API is modeled after the classes in
java.util.zip from the Java SDK. This means there are classes such
as Zip::ZipInputStream, Zip::ZipOutputStream and
Zip::ZipFile. Zip::ZipInputStream provides a basic interface for
iterating through the entries in a zip archive and reading from the
entries in the same way as from a regular File or IO
object. ZipOutputStream is the corresponding basic output
facility. Zip::ZipFile provides a mean for accessing the archives
central directory and provides means for accessing any entry without
having to iterate through the archive. Unlike Java's
java.util.zip.ZipFile rubyzip's Zip::ZipFile is mutable, which means
it can be used to change zip files as well.
Another way to access a zip archive with rubyzip is to use rubyzip's
Zip::ZipFileSystem API. Using this API files can be read from and
written to the archive in much the same manner as ruby's builtin
classes allows files to be read from and written to the file system.
rubyzip also features the
zip/ziprequire.rb[link:files/lib/zip/ziprequire_rb.html] module which
allows ruby to load ruby modules from zip archives.
For details about the specific behaviour of classes and methods refer
to the test suite. Finally you can generate the rdoc documentation or
visit http://rubyzip.sourceforge.net.
= License
rubyzip is distributed under the same license as ruby. See
http://www.ruby-lang.org/en/LICENSE.txt
= Website and Project Home
http://rubyzip.sourceforge.net
http://sourceforge.net/projects/rubyzip
== Download (tarballs and gems)
http://sourceforge.net/project/showfiles.php?group_id=43107&package_id=35377
= Authors
Thomas Sondergaard (thomas at sondergaard.cc)
Technorama Ltd. (oss-ruby-zip at technorama.net)
extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org)

View File

@ -1,16 +0,0 @@
* ZipInputStream: Support zip-files with trailing data descriptors
* Adjust rdoc stylesheet to advertise inherited methods if possible
* Suggestion: Add ZipFile/ZipInputStream example that demonstrates extracting all entries.
* Suggestion: ZipFile#extract destination should default to "."
* Suggestion: ZipEntry should have extract(), get_input_stream() methods etc
* SUggestion: ZipInputStream/ZipOutputStream should accept an IO object in addition to a filename.
* (is buffering used anywhere with write?)
* Inflater.sysread should pass the buffer to produce_input.
* Implement ZipFsDir.glob
* ZipFile.checkIntegrity method
* non-MSDOS permission attributes
** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2"
* Packager version, required unpacker version in zip headers
** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2"
* implement storing attributes and ownership information

View File

@ -1,165 +0,0 @@
module IOExtras #:nodoc:
CHUNK_SIZE = 131072
RANGE_ALL = 0..-1
def self.copy_stream(ostream, istream)
s = ''
ostream.write(istream.read(CHUNK_SIZE, s)) until istream.eof?
end
def self.copy_stream_n(ostream, istream, nbytes)
s = ''
toread = nbytes
while (toread > 0 && ! istream.eof?)
tr = toread > CHUNK_SIZE ? CHUNK_SIZE : toread
ostream.write(istream.read(tr, s))
toread -= tr
end
end
# Implements kind_of? in order to pretend to be an IO object
module FakeIO
def kind_of?(object)
object == IO || super
end
end
# Implements many of the convenience methods of IO
# such as gets, getc, readline and readlines
# depends on: input_finished?, produce_input and read
module AbstractInputStream
include Enumerable
include FakeIO
def initialize
super
@lineno = 0
@outputBuffer = ""
end
attr_accessor :lineno
def read(numberOfBytes = nil, buf = nil)
tbuf = nil
if @outputBuffer.length > 0
if numberOfBytes <= @outputBuffer.length
tbuf = @outputBuffer.slice!(0, numberOfBytes)
else
numberOfBytes -= @outputBuffer.length if (numberOfBytes)
rbuf = sysread(numberOfBytes, buf)
tbuf = @outputBuffer
tbuf << rbuf if (rbuf)
@outputBuffer = ""
end
else
tbuf = sysread(numberOfBytes, buf)
end
return nil unless (tbuf)
if buf
buf.replace(tbuf)
else
buf = tbuf
end
buf
end
def readlines(aSepString = $/)
retVal = []
each_line(aSepString) { |line| retVal << line }
return retVal
end
def gets(aSepString=$/)
@lineno = @lineno.next
return read if aSepString == nil
aSepString="#{$/}#{$/}" if aSepString == ""
bufferIndex=0
while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil)
bufferIndex=@outputBuffer.length
if input_finished?
return @outputBuffer.empty? ? nil : flush
end
@outputBuffer << produce_input
end
sepIndex=matchIndex + aSepString.length
return @outputBuffer.slice!(0...sepIndex)
end
def flush
retVal=@outputBuffer
@outputBuffer=""
return retVal
end
def readline(aSepString = $/)
retVal = gets(aSepString)
raise EOFError if retVal == nil
return retVal
end
def each_line(aSepString = $/)
while true
yield readline(aSepString)
end
rescue EOFError
end
alias_method :each, :each_line
end
# Implements many of the output convenience methods of IO.
# relies on <<
module AbstractOutputStream
include FakeIO
def write(data)
self << data
data.to_s.length
end
def print(*params)
self << params.join << $\.to_s
end
def printf(aFormatString, *params)
self << sprintf(aFormatString, *params)
end
def putc(anObject)
self << case anObject
when Fixnum then anObject.chr
when String then anObject
else raise TypeError, "putc: Only Fixnum and String supported"
end
anObject
end
def puts(*params)
params << "\n" if params.empty?
params.flatten.each {
|element|
val = element.to_s
self << val
self << "\n" unless val[-1,1] == "\n"
}
end
end
end # IOExtras namespace module
# Copyright (C) 2002-2004 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

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