Merge pull request #1 from dmaloney-r7/wordpress-xmlrpc-login-scanner
Wordpress xmlrpc login scannerbug/bundler_fix
commit
262b5413bc
|
@ -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
|
||||
|
|
74
.rubocop.yml
74
.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:
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`.
|
||||
|
||||
|
|
85
Gemfile
85
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 '<name>', [<version requirements>]
|
||||
gemspec
|
||||
|
||||
group :db do
|
||||
# Needed for Msf::DbManager
|
||||
gem 'activerecord', '>= 3.0.0', '< 4.0.0'
|
||||
# Metasploit::Credential database models
|
||||
gem 'metasploit-credential', '>= 0.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.
|
||||
|
|
|
@ -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
|
||||
|
|
227
Gemfile.lock
227
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
|
||||
|
|
86
Rakefile
86
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
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
require 'metasploit/framework/file_path_validator'
|
||||
require 'metasploit/framework/executable_path_validator'
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
# Load the rails application
|
||||
require File.expand_path('../application', __FILE__)
|
||||
|
||||
# Initialize the rails application
|
||||
Metasploit::Framework::Application.initialize!
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
119
db/schema.rb
119
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
case ARGV[0]
|
||||
when 'size'
|
||||
puts "30 134"
|
||||
when '-a'
|
||||
puts <<EOS
|
||||
speed 38400 baud; 30 rows; 134 columns;
|
||||
lflags: icanon isig iexten echo echoe echok echoke -echonl echoctl
|
||||
-echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo
|
||||
-extproc
|
||||
iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel iutf8
|
||||
-ignbrk brkint -inpck -ignpar -parmrk
|
||||
oflags: opost onlcr -oxtabs -onocr -onlret
|
||||
cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow
|
||||
-dtrflow -mdmbuf
|
||||
cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
|
||||
eol2 = <undef>; 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
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
Before do
|
||||
set_env('RAILS_ENV', 'test')
|
||||
@aruba_timeout_seconds = 3.minutes
|
||||
end
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -1,3 +1,29 @@
|
|||
#
|
||||
# Gems
|
||||
#
|
||||
# gems must load explicitly any gem declared in gemspec
|
||||
# @see https://github.com/bundler/bundler/issues/2018#issuecomment-6819359
|
||||
#
|
||||
|
||||
require 'active_support'
|
||||
require 'bcrypt'
|
||||
require 'json'
|
||||
require 'msgpack'
|
||||
require 'metasploit/model'
|
||||
require 'nokogiri'
|
||||
require 'packetfu'
|
||||
# railties has not autorequire defined
|
||||
# rkelly-remix is a fork of rkelly, so it's autorequire is 'rkelly' and not 'rkelly-remix'
|
||||
require 'rkelly'
|
||||
require 'robots'
|
||||
require 'zip'
|
||||
|
||||
#
|
||||
# Project
|
||||
#
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
# Top-level namespace that is shared between {Metasploit::Framework
|
||||
# metasploit-framework} and pro, which uses Metasploit::Pro.
|
||||
module Metasploit
|
||||
|
@ -5,30 +31,6 @@ module Metasploit
|
|||
# works in compatible manner with activerecord's rake tasks and other
|
||||
# railties.
|
||||
module Framework
|
||||
# Returns the environment for {Metasploit::Framework}. Checks
|
||||
# `METASPLOIT_FRAMEWORK_ENV` environment variable for value. Defaults to
|
||||
# `'development'` if `METASPLOIT_FRAMEWORK_ENV` is not set in the
|
||||
# environment variables.
|
||||
#
|
||||
# {env} is a ActiveSupport::StringInquirer like `Rails.env` so it can be
|
||||
# queried for its value.
|
||||
#
|
||||
# @example check if environment is development
|
||||
# if Metasploit::Framework.env.development?
|
||||
# # runs only when in development
|
||||
# end
|
||||
#
|
||||
# @return [ActiveSupport::StringInquirer] the environment name
|
||||
def self.env
|
||||
unless instance_variable_defined? :@env
|
||||
name = ENV['METASPLOIT_FRAMEWORK_ENV']
|
||||
name ||= 'development'
|
||||
@env = ActiveSupport::StringInquirer.new(name)
|
||||
end
|
||||
|
||||
@env
|
||||
end
|
||||
|
||||
# Returns the root of the metasploit-framework project. Use in place of
|
||||
# `Rails.root`.
|
||||
#
|
||||
|
@ -42,4 +44,4 @@ module Metasploit
|
|||
@root
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module Metasploit
|
||||
module Framework
|
||||
module API
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,74 @@
|
|||
require 'metasploit/framework/credential'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
|
||||
# This class is responsible for taking datastore options from the snmp_login module
|
||||
# and yielding appropriate {Metasploit::Framework::Credential}s to the {Metasploit::Framework::LoginScanner::SNMP}.
|
||||
# This one has to be different from {credentialCollection} as it will only have a {Metasploit::Framework::Credential#public}
|
||||
# It may be slightly confusing that the attribues are called password and pass_file, because this is what the legacy
|
||||
# module used. However, community Strings are now considered more to be public credentials than private ones.
|
||||
class CommunityStringCollection
|
||||
# @!attribute pass_file
|
||||
# Path to a file containing passwords, one per line
|
||||
# @return [String]
|
||||
attr_accessor :pass_file
|
||||
|
||||
# @!attribute password
|
||||
# @return [String]
|
||||
attr_accessor :password
|
||||
|
||||
# @!attribute prepended_creds
|
||||
# List of credentials to be tried before any others
|
||||
#
|
||||
# @see #prepend_cred
|
||||
# @return [Array<Credential>]
|
||||
attr_accessor :prepended_creds
|
||||
|
||||
# @option opts [String] :pass_file See {#pass_file}
|
||||
# @option opts [String] :password See {#password}
|
||||
# @option opts [Array<Credential>] :prepended_creds ([]) See {#prepended_creds}
|
||||
def initialize(opts = {})
|
||||
opts.each do |attribute, value|
|
||||
public_send("#{attribute}=", value)
|
||||
end
|
||||
self.prepended_creds ||= []
|
||||
end
|
||||
|
||||
# Combines all the provided credential sources into a stream of {Credential}
|
||||
# objects, yielding them one at a time
|
||||
#
|
||||
# @yieldparam credential [Metasploit::Framework::Credential]
|
||||
# @return [void]
|
||||
def each
|
||||
begin
|
||||
if pass_file.present?
|
||||
pass_fd = File.open(pass_file, 'r:binary')
|
||||
pass_fd.each_line do |line|
|
||||
line.chomp!
|
||||
yield Metasploit::Framework::Credential.new(public: line, paired: false)
|
||||
end
|
||||
end
|
||||
|
||||
if password.present?
|
||||
yield Metasploit::Framework::Credential.new(public: password, paired: false)
|
||||
end
|
||||
|
||||
ensure
|
||||
pass_fd.close if pass_fd && !pass_fd.closed?
|
||||
end
|
||||
end
|
||||
|
||||
# Add {Credential credentials} that will be yielded by {#each}
|
||||
#
|
||||
# @see prepended_creds
|
||||
# @param cred [Credential]
|
||||
# @return [self]
|
||||
def prepend_cred(cred)
|
||||
prepended_creds.unshift cred
|
||||
self
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
module Metasploit
|
||||
module Framework
|
||||
module Core
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -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<Credential>]
|
||||
attr_accessor :prepended_creds
|
||||
|
||||
# @!attribute realm
|
||||
# @return [String]
|
||||
attr_accessor :realm
|
||||
|
||||
# @!attribute user_as_pass
|
||||
# Whether each username should be tried as a password for that user
|
||||
# @return [Boolean]
|
||||
attr_accessor :user_as_pass
|
||||
|
||||
# @!attribute user_file
|
||||
# Path to a file containing usernames, one per line
|
||||
# @return [String]
|
||||
attr_accessor :user_file
|
||||
|
||||
# @!attribute username
|
||||
# @return [String]
|
||||
attr_accessor :username
|
||||
|
||||
# @!attribute userpass_file
|
||||
# Path to a file containing usernames and passwords separated by a space,
|
||||
# one pair per line
|
||||
# @return [String]
|
||||
attr_accessor :userpass_file
|
||||
|
||||
# @option opts [Boolean] :blank_passwords See {#blank_passwords}
|
||||
# @option opts [String] :pass_file See {#pass_file}
|
||||
# @option opts [String] :password See {#password}
|
||||
# @option opts [Array<Credential>] :prepended_creds ([]) See {#prepended_creds}
|
||||
# @option opts [Boolean] :user_as_pass See {#user_as_pass}
|
||||
# @option opts [String] :user_file See {#user_file}
|
||||
# @option opts [String] :username See {#username}
|
||||
# @option opts [String] :userpass_file See {#userpass_file}
|
||||
def initialize(opts = {})
|
||||
opts.each do |attribute, value|
|
||||
public_send("#{attribute}=", value)
|
||||
end
|
||||
self.prepended_creds ||= []
|
||||
end
|
||||
|
||||
# Add {Credential credentials} that will be yielded by {#each}
|
||||
#
|
||||
# @see prepended_creds
|
||||
# @param cred [Credential]
|
||||
# @return [self]
|
||||
def prepend_cred(cred)
|
||||
prepended_creds.unshift cred
|
||||
self
|
||||
end
|
||||
|
||||
# Combines all the provided credential sources into a stream of {Credential}
|
||||
# objects, yielding them one at a time
|
||||
#
|
||||
# @yieldparam credential [Metasploit::Framework::Credential]
|
||||
# @return [void]
|
||||
def each
|
||||
if pass_file.present?
|
||||
pass_fd = File.open(pass_file, 'r:binary')
|
||||
end
|
||||
|
||||
prepended_creds.each { |c| yield c }
|
||||
|
||||
if username.present?
|
||||
if password.present?
|
||||
yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm, 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
|
|
@ -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<Pathname>]
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -0,0 +1,276 @@
|
|||
require 'metasploit/framework/tcp/client'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module Ftp
|
||||
module Client
|
||||
include Metasploit::Framework::Tcp::Client
|
||||
|
||||
#
|
||||
# This method establishes an FTP connection to host and port specified by
|
||||
# the 'rhost' and 'rport' methods. After connecting, the banner
|
||||
# message is read in and stored in the 'banner' attribute.
|
||||
#
|
||||
def connect(global = true)
|
||||
fd = super(global)
|
||||
|
||||
@ftpbuff = '' unless @ftpbuff
|
||||
|
||||
# Wait for a banner to arrive...
|
||||
self.banner = recv_ftp_resp(fd)
|
||||
|
||||
# Return the file descriptor to the caller
|
||||
fd
|
||||
end
|
||||
|
||||
#
|
||||
# This method handles establishing datasocket for data channel
|
||||
#
|
||||
def data_connect(mode = nil, nsock = self.sock)
|
||||
if mode
|
||||
res = send_cmd([ 'TYPE' , mode ], true, nsock)
|
||||
return nil if not res =~ /^200/
|
||||
end
|
||||
|
||||
# force datasocket to renegotiate
|
||||
self.datasocket.shutdown if self.datasocket != nil
|
||||
|
||||
res = send_cmd(['PASV'], true, nsock)
|
||||
return nil if not res =~ /^227/
|
||||
|
||||
# 227 Entering Passive Mode (127,0,0,1,196,5)
|
||||
if res =~ /\((\d+)\,(\d+),(\d+),(\d+),(\d+),(\d+)/
|
||||
# convert port to FTP syntax
|
||||
datahost = "#{$1}.#{$2}.#{$3}.#{$4}"
|
||||
dataport = ($5.to_i * 256) + $6.to_i
|
||||
self.datasocket = Rex::Socket::Tcp.create('PeerHost' => datahost, 'PeerPort' => dataport)
|
||||
end
|
||||
self.datasocket
|
||||
end
|
||||
|
||||
#
|
||||
# This method handles disconnecting our data channel
|
||||
#
|
||||
def data_disconnect
|
||||
self.datasocket.shutdown
|
||||
self.datasocket = nil
|
||||
end
|
||||
|
||||
#
|
||||
# Connect and login to the remote FTP server using the credentials
|
||||
# that have been supplied in the exploit options.
|
||||
#
|
||||
def connect_login(user,pass,global = true)
|
||||
ftpsock = connect(global)
|
||||
|
||||
if !(user and pass)
|
||||
return false
|
||||
end
|
||||
|
||||
res = send_user(user, ftpsock)
|
||||
|
||||
if (res !~ /^(331|2)/)
|
||||
return false
|
||||
end
|
||||
|
||||
if (pass)
|
||||
res = send_pass(pass, ftpsock)
|
||||
if (res !~ /^2/)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
#
|
||||
# This method logs in as the supplied user by transmitting the FTP
|
||||
# 'USER <user>' command.
|
||||
#
|
||||
def send_user(user, nsock = self.sock)
|
||||
raw_send("USER #{user}\r\n", nsock)
|
||||
recv_ftp_resp(nsock)
|
||||
end
|
||||
|
||||
#
|
||||
# This method completes user authentication by sending the supplied
|
||||
# password using the FTP 'PASS <pass>' command.
|
||||
#
|
||||
def send_pass(pass, nsock = self.sock)
|
||||
raw_send("PASS #{pass}\r\n", nsock)
|
||||
recv_ftp_resp(nsock)
|
||||
end
|
||||
|
||||
#
|
||||
# This method sends a QUIT command.
|
||||
#
|
||||
def send_quit(nsock = self.sock)
|
||||
raw_send("QUIT\r\n", nsock)
|
||||
recv_ftp_resp(nsock)
|
||||
end
|
||||
|
||||
#
|
||||
# This method sends one command with zero or more parameters
|
||||
#
|
||||
def send_cmd(args, recv = true, nsock = self.sock)
|
||||
cmd = args.join(" ") + "\r\n"
|
||||
ret = raw_send(cmd, nsock)
|
||||
if (recv)
|
||||
return recv_ftp_resp(nsock)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
#
|
||||
# This method transmits the command in args and receives / uploads DATA via data channel
|
||||
# For commands not needing data, it will fall through to the original send_cmd
|
||||
#
|
||||
# For commands that send data only, the return will be the server response.
|
||||
# For commands returning both data and a server response, an array will be returned.
|
||||
#
|
||||
# NOTE: This function always waits for a response from the server.
|
||||
#
|
||||
def send_cmd_data(args, data, mode = 'a', nsock = self.sock)
|
||||
type = nil
|
||||
# implement some aliases for various commands
|
||||
if (args[0] =~ /^DIR$/i || args[0] =~ /^LS$/i)
|
||||
# TODO || args[0] =~ /^MDIR$/i || args[0] =~ /^MLS$/i
|
||||
args[0] = "LIST"
|
||||
type = "get"
|
||||
elsif (args[0] =~ /^GET$/i)
|
||||
args[0] = "RETR"
|
||||
type = "get"
|
||||
elsif (args[0] =~ /^PUT$/i)
|
||||
args[0] = "STOR"
|
||||
type = "put"
|
||||
end
|
||||
|
||||
# fall back if it's not a supported data command
|
||||
if not type
|
||||
return send_cmd(args, true, nsock)
|
||||
end
|
||||
|
||||
# Set the transfer mode and connect to the remove server
|
||||
return nil if not data_connect(mode)
|
||||
|
||||
# Our pending command should have got a connection now.
|
||||
res = send_cmd(args, true, nsock)
|
||||
# make sure could open port
|
||||
return nil unless res =~ /^(150|125) /
|
||||
|
||||
# dispatch to the proper method
|
||||
if (type == "get")
|
||||
# failed listings jsut disconnect..
|
||||
begin
|
||||
data = self.datasocket.get_once(-1, ftp_timeout)
|
||||
rescue ::EOFError
|
||||
data = nil
|
||||
end
|
||||
else
|
||||
sent = self.datasocket.put(data)
|
||||
end
|
||||
|
||||
# close data channel so command channel updates
|
||||
data_disconnect
|
||||
|
||||
# get status of transfer
|
||||
ret = nil
|
||||
if (type == "get")
|
||||
ret = recv_ftp_resp(nsock)
|
||||
ret = [ ret, data ]
|
||||
else
|
||||
ret = recv_ftp_resp(nsock)
|
||||
end
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
#
|
||||
# This method transmits a FTP command and waits for a response. If one is
|
||||
# received, it is returned to the caller.
|
||||
#
|
||||
def raw_send_recv(cmd, nsock = self.sock)
|
||||
nsock.put(cmd)
|
||||
nsock.get_once(-1, ftp_timeout)
|
||||
end
|
||||
|
||||
#
|
||||
# This method reads an FTP response based on FTP continuation stuff
|
||||
#
|
||||
def recv_ftp_resp(nsock = self.sock)
|
||||
found_end = false
|
||||
resp = ""
|
||||
left = ""
|
||||
if !@ftpbuff.empty?
|
||||
left << @ftpbuff
|
||||
@ftpbuff = ""
|
||||
end
|
||||
while true
|
||||
data = nsock.get_once(-1, ftp_timeout)
|
||||
if not data
|
||||
@ftpbuff << resp
|
||||
@ftpbuff << left
|
||||
return data
|
||||
end
|
||||
|
||||
got = left + data
|
||||
left = ""
|
||||
|
||||
# handle the end w/o newline case
|
||||
enlidx = got.rindex(0x0a.chr)
|
||||
if enlidx != (got.length-1)
|
||||
if not enlidx
|
||||
left << got
|
||||
next
|
||||
else
|
||||
left << got.slice!((enlidx+1)..got.length)
|
||||
end
|
||||
end
|
||||
|
||||
# split into lines
|
||||
rarr = got.split(/\r?\n/)
|
||||
rarr.each do |ln|
|
||||
if not found_end
|
||||
resp << ln
|
||||
resp << "\r\n"
|
||||
if ln.length > 3 and ln[3,1] == ' '
|
||||
found_end = true
|
||||
end
|
||||
else
|
||||
left << ln
|
||||
left << "\r\n"
|
||||
end
|
||||
end
|
||||
if found_end
|
||||
@ftpbuff << left
|
||||
return resp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# This method transmits a FTP command and does not wait for a response
|
||||
#
|
||||
def raw_send(cmd, nsock = self.sock)
|
||||
nsock.put(cmd)
|
||||
end
|
||||
|
||||
def ftp_timeout
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
|
||||
protected
|
||||
|
||||
#
|
||||
# This attribute holds the banner that was read in after a successful call
|
||||
# to connect or connect_login.
|
||||
#
|
||||
attr_accessor :banner, :datasocket
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,429 @@
|
|||
require 'metasploit/framework/jtr/invalid_wordlist'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module JtR
|
||||
|
||||
class Wordlist
|
||||
include ActiveModel::Validations
|
||||
|
||||
# A mapping of the mutation substitution rules
|
||||
MUTATIONS = {
|
||||
'@' => 'a',
|
||||
'0' => 'o',
|
||||
'3' => 'e',
|
||||
'$' => 's',
|
||||
'7' => 't',
|
||||
'1' => 'l',
|
||||
'5' => 's'
|
||||
}
|
||||
|
||||
# @!attribute appenders
|
||||
# @return [Array] an array of strings to append to each word
|
||||
attr_accessor :appenders
|
||||
|
||||
# @!attribute custom_wordlist
|
||||
# @return [String] the path to a custom wordlist file to include
|
||||
attr_accessor :custom_wordlist
|
||||
|
||||
# @!attribute mutate
|
||||
# @return [TrueClass] if you want each word mutated as it is added
|
||||
# @return [FalseClass] if you do not want each word mutated
|
||||
attr_accessor :mutate
|
||||
|
||||
# @!attribute prependers
|
||||
# @return [Array] an array of strings to prepend to each word
|
||||
attr_accessor :prependers
|
||||
|
||||
# @!attribute use_common_root
|
||||
# @return [TrueClass] if you want to use the common root words wordlist
|
||||
# @return [FalseClass] if you do not want to use the common root words wordlist
|
||||
attr_accessor :use_common_root
|
||||
|
||||
# @!attribute use_creds
|
||||
# @return [TrueClass] if you want to seed the wordlist with existing credential data from the database
|
||||
# @return [FalseClass] if you do not want to seed the wordlist with existing credential data from the database
|
||||
attr_accessor :use_creds
|
||||
|
||||
# @!attribute use_db_info
|
||||
# @return [TrueClass] if you want to seed the wordlist with looted database names and schemas
|
||||
# @return [FalseClass] if you do not want to seed the wordlist with looted database names and schemas
|
||||
attr_accessor :use_db_info
|
||||
|
||||
# @!attribute use_default_wordlist
|
||||
# @return [TrueClass] if you want to use the default wordlist
|
||||
# @return [FalseClass] if you do not want to use the default wordlist
|
||||
attr_accessor :use_default_wordlist
|
||||
|
||||
# @!attribute use_hostnames
|
||||
# @return [TrueClass] if you want to seed the wordlist with existing hostnames from the database
|
||||
# @return [FalseClass] if you do not want to seed the wordlist with existing hostnames from the database
|
||||
attr_accessor :use_hostnames
|
||||
|
||||
# @!attribute workspace
|
||||
# @return [Mdm::Workspace] the workspace this cracker is for.
|
||||
attr_accessor :workspace
|
||||
|
||||
validates :custom_wordlist, :'Metasploit::Framework::File_path' => true, if: 'custom_wordlist.present?'
|
||||
|
||||
validates :mutate,
|
||||
inclusion: { in: [true, false], message: "must be true or false" }
|
||||
|
||||
|
||||
validates :use_common_root,
|
||||
inclusion: { in: [true, false], message: "must be true or false" }
|
||||
|
||||
validates :use_creds,
|
||||
inclusion: { in: [true, false], message: "must be true or false" }
|
||||
|
||||
validates :use_db_info,
|
||||
inclusion: { in: [true, false], message: "must be true or false" }
|
||||
|
||||
validates :use_default_wordlist,
|
||||
inclusion: { in: [true, false], message: "must be true or false" }
|
||||
|
||||
validates :use_hostnames,
|
||||
inclusion: { in: [true, false], message: "must be true or false" }
|
||||
|
||||
validates :workspace,
|
||||
presence: true
|
||||
|
||||
# @param attributes [Hash{Symbol => String,nil}]
|
||||
def initialize(attributes={})
|
||||
attributes.each do |attribute, value|
|
||||
public_send("#{attribute}=", value)
|
||||
end
|
||||
@appenders ||= []
|
||||
@prependers ||= []
|
||||
end
|
||||
|
||||
# This method takes a word, and appends each word from the appenders list
|
||||
# and yields the new words.
|
||||
#
|
||||
# @yieldparam word [String] the expanded word
|
||||
# @return [void]
|
||||
def each_appended_word(word='')
|
||||
yield word
|
||||
appenders.each do |suffix|
|
||||
yield "#{word}#{suffix}"
|
||||
end
|
||||
end
|
||||
|
||||
# This method checks all the attributes set on the object and calls
|
||||
# the appropriate enumerators for each option and yields the results back
|
||||
# up the call-chain.
|
||||
#
|
||||
# @yieldparam word [String] the expanded word
|
||||
# @return [void]
|
||||
def each_base_word
|
||||
# Make sure are attributes are all valid first!
|
||||
valid!
|
||||
|
||||
# Yield the expanded form of each line of the custom wordlist if one was given
|
||||
if custom_wordlist.present?
|
||||
each_custom_word do |word|
|
||||
yield word unless word.blank?
|
||||
end
|
||||
end
|
||||
|
||||
# Yield each word from the common root words list if it was selected
|
||||
if use_common_root
|
||||
each_root_word do |word|
|
||||
yield word unless word.blank?
|
||||
end
|
||||
end
|
||||
|
||||
# If the user has selected use_creds we yield each password, username, and realm name
|
||||
# that currently exists in the database.
|
||||
if use_creds
|
||||
each_cred_word do |word|
|
||||
yield word unless word.blank?
|
||||
end
|
||||
end
|
||||
|
||||
if use_db_info
|
||||
each_database_word do |word|
|
||||
yield word unless word.blank?
|
||||
end
|
||||
end
|
||||
|
||||
if use_default_wordlist
|
||||
each_default_word do |word|
|
||||
yield word unless word.blank?
|
||||
end
|
||||
end
|
||||
|
||||
if use_hostnames
|
||||
each_hostname_word do |word|
|
||||
yield word unless word.blank?
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# This method searches all saved Credentials in the database
|
||||
# and yields all passwords, usernames, and realm names it finds.
|
||||
#
|
||||
# @yieldparam word [String] the expanded word
|
||||
# @return [void]
|
||||
def each_cred_word
|
||||
# We don't want all Private types here. Only Passwords make sense for inclusion in the wordlist.
|
||||
Metasploit::Credential::Password.all.each do |password|
|
||||
yield password.data
|
||||
end
|
||||
|
||||
Metasploit::Credential::Public.all.each do |public|
|
||||
yield public.username
|
||||
end
|
||||
|
||||
Metasploit::Credential::Realm.all.each do |realm|
|
||||
yield realm.value
|
||||
end
|
||||
end
|
||||
|
||||
# This method reads the file provided as custom_wordlist and yields
|
||||
# the expanded form of each word in the list.
|
||||
#
|
||||
# @yieldparam word [String] the expanded word
|
||||
# @return [void]
|
||||
def each_custom_word
|
||||
::File.open(custom_wordlist, "rb") do |fd|
|
||||
fd.each_line do |line|
|
||||
expanded_words(line) do |word|
|
||||
yield word
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This method searches the notes in the current workspace
|
||||
# for DB instance names, database names, table names, and
|
||||
# column names gathered from live database servers. It yields
|
||||
# each one that it finds.
|
||||
#
|
||||
# @yieldparam word [String] the expanded word
|
||||
# @return [void]
|
||||
def each_database_word
|
||||
# Yield database, table and column names from any looted database schemas
|
||||
workspace.notes.where('ntype like ?', '%.schema%').each do |note|
|
||||
expanded_words(note.data['DBName']) do |word|
|
||||
yield word
|
||||
end
|
||||
|
||||
note.data['Tables'].each do |table|
|
||||
expanded_words(table['TableName']) do |word|
|
||||
yield word
|
||||
end
|
||||
|
||||
table['Columns'].each do |column|
|
||||
expanded_words(column['ColumnName']) do |word|
|
||||
yield word
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Yield any capture MSSQL Instance names
|
||||
workspace.notes.find(:all, :conditions => ['ntype=?', 'mssql.instancename']).each do |note|
|
||||
expanded_words(note.data['InstanceName']) do |word|
|
||||
yield word
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This method yields expanded words taken from the default john
|
||||
# wordlist that we ship in the data directory.
|
||||
#
|
||||
# @yieldparam word [String] the expanded word
|
||||
# @return [void]
|
||||
def each_default_word
|
||||
::File.open(default_wordlist_path, "rb") do |fd|
|
||||
fd.each_line do |line|
|
||||
expanded_words(line) do |word|
|
||||
yield word
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This method yields the expanded words out of all the hostnames
|
||||
# found in the current workspace.
|
||||
#
|
||||
# @yieldparam word [String] the expanded word
|
||||
# @return [void]
|
||||
def each_hostname_word
|
||||
workspace.hosts.all.each do |host|
|
||||
unless host.name.nil?
|
||||
expanded_words(host.name) do |word|
|
||||
yield nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This method checks to see if the user asked for mutations. If mutations
|
||||
# have been enabled, then it creates all the unique mutations and yields
|
||||
# each result.
|
||||
#
|
||||
# @yieldparam word [String] the expanded word
|
||||
# @return [void]
|
||||
def each_mutated_word(word='')
|
||||
mutants = [ ]
|
||||
|
||||
# Run the mutations only if the option is set
|
||||
if mutate
|
||||
mutants = mutants + mutate_word(word)
|
||||
end
|
||||
|
||||
mutants << word
|
||||
mutants.uniq.each do |mutant|
|
||||
yield mutant
|
||||
end
|
||||
end
|
||||
|
||||
# This method takes a word, and prepends each word from the prependers list
|
||||
# and yields the new words.
|
||||
#
|
||||
# @yieldparam word [String] the expanded word
|
||||
# @return [void]
|
||||
def each_prepended_word(word='')
|
||||
yield word
|
||||
prependers.each do |prefix|
|
||||
yield "#{prefix}#{word}"
|
||||
end
|
||||
end
|
||||
|
||||
# This method reads the common_roots.txt wordlist
|
||||
# expands any words in the list and yields them.
|
||||
#
|
||||
# @yieldparam word [String] the expanded word
|
||||
# @return [void]
|
||||
def each_root_word
|
||||
::File.open(common_root_words_path, "rb") do |fd|
|
||||
fd.each_line do |line|
|
||||
expanded_words(line) do |word|
|
||||
yield word
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This method wraps around all the other enumerators. It processes
|
||||
# all of the options and yields each word generated by the options
|
||||
# selected.
|
||||
#
|
||||
# @yieldparam word [String] the word to write out to the wordlist file
|
||||
# @return [void]
|
||||
def each_word
|
||||
each_base_word do |base_word|
|
||||
each_mutated_word(base_word) do |mutant|
|
||||
each_prepended_word(mutant) do |prepended|
|
||||
yield prepended
|
||||
end
|
||||
|
||||
each_appended_word(mutant) do |appended|
|
||||
yield appended
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This method takes a string and splits it on non-word characters
|
||||
# and the underscore. It does this to find likely distinct words
|
||||
# in the string. It then yields each 'word' found this way.
|
||||
#
|
||||
# @param word [String] the string to split apart
|
||||
# @yieldparam expanded [String] the expanded words
|
||||
# @return [void]
|
||||
def expanded_words(word='')
|
||||
word.split(/[\W_]+/).each do |expanded|
|
||||
yield expanded
|
||||
end
|
||||
end
|
||||
|
||||
# This method takes a word and applies various mutation rules to that word
|
||||
# and returns an array of all the mutated forms.
|
||||
#
|
||||
# @param word [String] the word to apply the mutations to
|
||||
# @return [Array<String>] An array containing all the mutated forms of the word
|
||||
def mutate_word(word)
|
||||
results = []
|
||||
# Iterate through combinations to create each possible mutation
|
||||
mutation_keys.each do |iteration|
|
||||
next if iteration.flatten.empty?
|
||||
intermediate = word.dup
|
||||
subsititutions = iteration.collect { |key| MUTATIONS[key] }
|
||||
intermediate.tr!(subsititutions.join, iteration.join)
|
||||
results << intermediate
|
||||
end
|
||||
results.flatten.uniq
|
||||
end
|
||||
|
||||
# A getter for a memoized version fo the mutation keys list
|
||||
#
|
||||
# @return [Array<Array>] a 2D array of all mutation combinations
|
||||
def mutation_keys
|
||||
@mutation_keys ||= generate_mutation_keys
|
||||
end
|
||||
|
||||
# This method takes all the options provided and streams the generated wordlist out
|
||||
# to a {Rex::Quickfile} and returns the {Rex::Quickfile}.
|
||||
#
|
||||
# @return [Rex::Quickfile] The {Rex::Quickfile} object that the wordlist has been written to
|
||||
def to_file
|
||||
valid!
|
||||
wordlist_file = Rex::Quickfile.new("jtrtmp")
|
||||
each_word do |word|
|
||||
wordlist_file.puts word
|
||||
end
|
||||
wordlist_file
|
||||
end
|
||||
|
||||
# Raise an exception if the attributes are not valid.
|
||||
#
|
||||
# @raise [Invalid] if the attributes are not valid on this scanner
|
||||
# @return [void]
|
||||
def valid!
|
||||
unless valid?
|
||||
raise Metasploit::Framework::JtR::InvalidWordlist.new(self)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
private
|
||||
|
||||
# This method returns the path to the common_roots.txt wordlist
|
||||
#
|
||||
# @return [String] the file path to the common_roots.txt file
|
||||
def common_root_words_path
|
||||
::File.join(Msf::Config.data_directory, 'john', 'wordlists', 'common_roots.txt')
|
||||
end
|
||||
|
||||
# This method returns the path to the passwords.lst wordlist
|
||||
#
|
||||
# @return [String] the file path to the passwords.lst file
|
||||
def default_wordlist_path
|
||||
::File.join(Msf::Config.data_directory, 'john', 'wordlists', 'password.lst')
|
||||
end
|
||||
|
||||
def generate_mutation_keys
|
||||
iterations = MUTATIONS.keys.dup
|
||||
|
||||
# Find PowerSet of all possible mutation combinations
|
||||
iterations.inject([[]]) do |accumulator,mutation_key|
|
||||
power_set = []
|
||||
accumulator.each do |i|
|
||||
power_set << i
|
||||
power_set << i+[mutation_key]
|
||||
end
|
||||
power_set
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
require 'metasploit/framework/credential'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
# This module provides the namespace for all LoginScanner classes.
|
||||
# LoginScanners are the classes that provide functionality for testing
|
||||
# authentication against various different protocols and mechanisms.
|
||||
module LoginScanner
|
||||
require 'metasploit/framework/login_scanner/result'
|
||||
require 'metasploit/framework/login_scanner/invalid'
|
||||
|
||||
# Gather a list of LoginScanner classes that can potentially be
|
||||
# used for a given `service`, which should usually be an
|
||||
# `Mdm::Service` object, but can be anything that responds to
|
||||
# #name and #port.
|
||||
#
|
||||
# @param service [Mdm::Service,#port,#name]
|
||||
# @return [Array<LoginScanner::Base>] A collection of LoginScanner
|
||||
# classes that will probably give useful results when run
|
||||
# against `service`.
|
||||
def self.classes_for_service(service)
|
||||
|
||||
unless @required
|
||||
# Make sure we've required all the scanner classes
|
||||
dir = File.expand_path("../login_scanner/", __FILE__)
|
||||
Dir.glob(File.join(dir, "*.rb")).each do |f|
|
||||
require f if File.file?(f)
|
||||
end
|
||||
@required = true
|
||||
end
|
||||
|
||||
self.constants.map{|sym| const_get(sym)}.select do |const|
|
||||
next unless const.kind_of?(Class)
|
||||
|
||||
(
|
||||
const.const_defined?(:LIKELY_PORTS) &&
|
||||
const.const_get(:LIKELY_PORTS).include?(service.port)
|
||||
) || (
|
||||
const.const_defined?(:LIKELY_SERVICE_NAMES) &&
|
||||
const.const_get(:LIKELY_SERVICE_NAMES).include?(service.name)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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 =~ /<value><int>401<\/int><\/value>/ || response.body =~ /<name>user_id<\/name>/
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response)
|
||||
elsif res.body =~ /<value><int>-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 version=\"1.0\" encoding=\"iso-8859-1\"?>"
|
||||
xml << '<methodCall>'
|
||||
xml << '<methodName>wp.getUsers</methodName>'
|
||||
xml << '<params><param><value>1</value></param>'
|
||||
xml << "<param><value>#{user}</value></param>"
|
||||
xml << "<param><value>#{pass}</value></param>"
|
||||
xml << '</params>'
|
||||
xml << '</methodCall>'
|
||||
xml
|
||||
end
|
||||
|
||||
# (see Base#set_sane_defaults)
|
||||
def set_sane_defaults
|
||||
@method = "POST".freeze
|
||||
super
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,728 @@
|
|||
require 'metasploit/framework/tcp/client'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module MSSQL
|
||||
|
||||
module Client
|
||||
include Metasploit::Framework::Tcp::Client
|
||||
|
||||
NTLM_CRYPT = Rex::Proto::NTLM::Crypt
|
||||
NTLM_CONST = Rex::Proto::NTLM::Constants
|
||||
NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||
NTLM_XCEPT = Rex::Proto::NTLM::Exceptions
|
||||
|
||||
# Encryption
|
||||
ENCRYPT_OFF = 0x00 #Encryption is available but off.
|
||||
ENCRYPT_ON = 0x01 #Encryption is available and on.
|
||||
ENCRYPT_NOT_SUP = 0x02 #Encryption is not available.
|
||||
ENCRYPT_REQ = 0x03 #Encryption is required.
|
||||
|
||||
# Packet Type
|
||||
TYPE_SQL_BATCH = 1 # (Client) SQL command
|
||||
TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused)
|
||||
TYPE_RPC = 3 # (Client) RPC
|
||||
TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters,
|
||||
# Request Completion, Error and Info Messages, Attention Acknowledgement
|
||||
TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention
|
||||
TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data
|
||||
TYPE_TRANSACTION_MANAGER_REQUEST = 14 # (Client) Transaction request manager
|
||||
TYPE_TDS7_LOGIN = 16 # (Client) Login
|
||||
TYPE_SSPI_MESSAGE = 17 # (Client) Login
|
||||
TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 7
|
||||
|
||||
# Status
|
||||
STATUS_NORMAL = 0x00
|
||||
STATUS_END_OF_MESSAGE = 0x01
|
||||
STATUS_IGNORE_EVENT = 0x02
|
||||
STATUS_RESETCONNECTION = 0x08 # TDS 7.1+
|
||||
STATUS_RESETCONNECTIONSKIPTRAN = 0x10 # TDS 7.3+
|
||||
|
||||
#
|
||||
# This method connects to the server over TCP and attempts
|
||||
# to authenticate with the supplied username and password
|
||||
# The global socket is used and left connected after auth
|
||||
#
|
||||
def mssql_login(user='sa', pass='', db='', domain_name='')
|
||||
|
||||
disconnect if self.sock
|
||||
connect
|
||||
|
||||
# Send a prelogin packet and check that encryption is not enabled
|
||||
if mssql_prelogin() != ENCRYPT_NOT_SUP
|
||||
print_error("Encryption is not supported")
|
||||
return false
|
||||
end
|
||||
|
||||
if windows_authentication
|
||||
idx = 0
|
||||
pkt = ''
|
||||
pkt_hdr = ''
|
||||
pkt_hdr = [
|
||||
TYPE_TDS7_LOGIN, #type
|
||||
STATUS_END_OF_MESSAGE, #status
|
||||
0x0000, #length
|
||||
0x0000, # SPID
|
||||
0x01, # PacketID (unused upon specification
|
||||
# but ms network monitor stil prefer 1 to decode correctly, wireshark don't care)
|
||||
0x00 #Window
|
||||
]
|
||||
|
||||
pkt << [
|
||||
0x00000000, # Size
|
||||
0x71000001, # TDS Version
|
||||
0x00000000, # Dummy Size
|
||||
0x00000007, # Version
|
||||
rand(1024+1), # PID
|
||||
0x00000000, # ConnectionID
|
||||
0xe0, # Option Flags 1
|
||||
0x83, # Option Flags 2
|
||||
0x00, # SQL Type Flags
|
||||
0x00, # Reserved Flags
|
||||
0x00000000, # Time Zone
|
||||
0x00000000 # Collation
|
||||
].pack('VVVVVVCCCCVV')
|
||||
|
||||
cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
|
||||
aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) #application and library name
|
||||
sname = Rex::Text.to_unicode( rhost )
|
||||
dname = Rex::Text.to_unicode( db )
|
||||
|
||||
ntlm_options = {
|
||||
:signing => false,
|
||||
:usentlm2_session => use_ntlm2_session,
|
||||
:use_ntlmv2 => use_ntlmv2,
|
||||
:send_lm => send_lm,
|
||||
:send_ntlm => send_ntlm
|
||||
}
|
||||
|
||||
ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options)
|
||||
workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)
|
||||
|
||||
ntlmsspblob = NTLM_UTILS::make_ntlmssp_blob_init(domain_name, workstation_name, ntlmssp_flags)
|
||||
|
||||
idx = pkt.size + 50 # lengths below
|
||||
|
||||
pkt << [idx, cname.length / 2].pack('vv')
|
||||
idx += cname.length
|
||||
|
||||
pkt << [0, 0].pack('vv') # User length offset must be 0
|
||||
pkt << [0, 0].pack('vv') # Password length offset must be 0
|
||||
|
||||
pkt << [idx, aname.length / 2].pack('vv')
|
||||
idx += aname.length
|
||||
|
||||
pkt << [idx, sname.length / 2].pack('vv')
|
||||
idx += sname.length
|
||||
|
||||
pkt << [0, 0].pack('vv') # unused
|
||||
|
||||
pkt << [idx, aname.length / 2].pack('vv')
|
||||
idx += aname.length
|
||||
|
||||
pkt << [idx, 0].pack('vv') # locales
|
||||
|
||||
pkt << [idx, 0].pack('vv') #db
|
||||
|
||||
# ClientID (should be mac address)
|
||||
pkt << Rex::Text.rand_text(6)
|
||||
|
||||
# NTLMSSP
|
||||
pkt << [idx, ntlmsspblob.length].pack('vv')
|
||||
idx += ntlmsspblob.length
|
||||
|
||||
pkt << [idx, 0].pack('vv') # AtchDBFile
|
||||
|
||||
pkt << cname
|
||||
pkt << aname
|
||||
pkt << sname
|
||||
pkt << aname
|
||||
pkt << ntlmsspblob
|
||||
|
||||
# Total packet length
|
||||
pkt[0,4] = [pkt.length].pack('V')
|
||||
|
||||
pkt_hdr[2] = pkt.length + 8
|
||||
|
||||
pkt = pkt_hdr.pack("CCnnCC") + pkt
|
||||
|
||||
# Rem : One have to set check_status to false here because sql server sp0 (and maybe above)
|
||||
# has a strange behavior that differs from the specifications
|
||||
# upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header
|
||||
# is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification
|
||||
resp = mssql_send_recv(pkt,15, false)
|
||||
|
||||
# Get default data
|
||||
begin
|
||||
blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(resp)
|
||||
# a domain.length < 3 will hit this
|
||||
rescue NTLM_XCEPT::NTLMMissingChallenge
|
||||
return false
|
||||
end
|
||||
|
||||
challenge_key = blob_data[:challenge_key]
|
||||
server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error
|
||||
#netbios name
|
||||
default_name = blob_data[:default_name] || ''
|
||||
#netbios domain
|
||||
default_domain = blob_data[:default_domain] || ''
|
||||
#dns name
|
||||
dns_host_name = blob_data[:dns_host_name] || ''
|
||||
#dns domain
|
||||
dns_domain_name = blob_data[:dns_domain_name] || ''
|
||||
#Client time
|
||||
chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || ''
|
||||
|
||||
spnopt = {:use_spn => send_spn, :name => self.rhost}
|
||||
|
||||
resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(user, pass, challenge_key,
|
||||
domain_name, default_name, default_domain,
|
||||
dns_host_name, dns_domain_name, chall_MsvAvTimestamp,
|
||||
spnopt, ntlm_options)
|
||||
|
||||
ntlmssp = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, user, resp_lm, resp_ntlm, '', ntlmssp_flags)
|
||||
|
||||
# Create an SSPIMessage
|
||||
idx = 0
|
||||
pkt = ''
|
||||
pkt_hdr = ''
|
||||
pkt_hdr = [
|
||||
TYPE_SSPI_MESSAGE, #type
|
||||
STATUS_END_OF_MESSAGE, #status
|
||||
0x0000, #length
|
||||
0x0000, # SPID
|
||||
0x01, # PacketID
|
||||
0x00 #Window
|
||||
]
|
||||
|
||||
pkt_hdr[2] = ntlmssp.length + 8
|
||||
|
||||
pkt = pkt_hdr.pack("CCnnCC") + ntlmssp
|
||||
|
||||
resp = mssql_send_recv(pkt)
|
||||
|
||||
|
||||
#SQL Server Authentification
|
||||
else
|
||||
idx = 0
|
||||
pkt = ''
|
||||
pkt << [
|
||||
0x00000000, # Dummy size
|
||||
|
||||
0x71000001, # TDS Version
|
||||
0x00000000, # Size
|
||||
0x00000007, # Version
|
||||
rand(1024+1), # PID
|
||||
0x00000000, # ConnectionID
|
||||
0xe0, # Option Flags 1
|
||||
0x03, # Option Flags 2
|
||||
0x00, # SQL Type Flags
|
||||
0x00, # Reserved Flags
|
||||
0x00000000, # Time Zone
|
||||
0x00000000 # Collation
|
||||
].pack('VVVVVVCCCCVV')
|
||||
|
||||
|
||||
cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
|
||||
uname = Rex::Text.to_unicode( user )
|
||||
pname = mssql_tds_encrypt( pass )
|
||||
aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
|
||||
sname = Rex::Text.to_unicode( rhost )
|
||||
dname = Rex::Text.to_unicode( db )
|
||||
|
||||
idx = pkt.size + 50 # lengths below
|
||||
|
||||
pkt << [idx, cname.length / 2].pack('vv')
|
||||
idx += cname.length
|
||||
|
||||
pkt << [idx, uname.length / 2].pack('vv')
|
||||
idx += uname.length
|
||||
|
||||
pkt << [idx, pname.length / 2].pack('vv')
|
||||
idx += pname.length
|
||||
|
||||
pkt << [idx, aname.length / 2].pack('vv')
|
||||
idx += aname.length
|
||||
|
||||
pkt << [idx, sname.length / 2].pack('vv')
|
||||
idx += sname.length
|
||||
|
||||
pkt << [0, 0].pack('vv')
|
||||
|
||||
pkt << [idx, aname.length / 2].pack('vv')
|
||||
idx += aname.length
|
||||
|
||||
pkt << [idx, 0].pack('vv')
|
||||
|
||||
pkt << [idx, dname.length / 2].pack('vv')
|
||||
idx += dname.length
|
||||
|
||||
# The total length has to be embedded twice more here
|
||||
pkt << [
|
||||
0,
|
||||
0,
|
||||
0x12345678,
|
||||
0x12345678
|
||||
].pack('vVVV')
|
||||
|
||||
pkt << cname
|
||||
pkt << uname
|
||||
pkt << pname
|
||||
pkt << aname
|
||||
pkt << sname
|
||||
pkt << aname
|
||||
pkt << dname
|
||||
|
||||
# Total packet length
|
||||
pkt[0,4] = [pkt.length].pack('V')
|
||||
|
||||
# Embedded packet lengths
|
||||
pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2
|
||||
|
||||
# Packet header and total length including header
|
||||
pkt = "\x10\x01" + [pkt.length + 8].pack('n') + [0].pack('n') + [1].pack('C') + "\x00" + pkt
|
||||
|
||||
resp = mssql_send_recv(pkt)
|
||||
|
||||
end
|
||||
|
||||
info = {:errors => []}
|
||||
info = mssql_parse_reply(resp,info)
|
||||
|
||||
return false if not info
|
||||
info[:login_ack] ? true : false
|
||||
end
|
||||
|
||||
#
|
||||
# Parse an "environment change" TDS token
|
||||
#
|
||||
def mssql_parse_env(data, info)
|
||||
len = data.slice!(0,2).unpack('v')[0]
|
||||
buff = data.slice!(0,len)
|
||||
type = buff.slice!(0,1).unpack('C')[0]
|
||||
|
||||
nval = ''
|
||||
nlen = buff.slice!(0,1).unpack('C')[0] || 0
|
||||
nval = buff.slice!(0,nlen*2).gsub("\x00", '') if nlen > 0
|
||||
|
||||
oval = ''
|
||||
olen = buff.slice!(0,1).unpack('C')[0] || 0
|
||||
oval = buff.slice!(0,olen*2).gsub("\x00", '') if olen > 0
|
||||
|
||||
info[:envs] ||= []
|
||||
info[:envs] << { :type => type, :old => oval, :new => nval }
|
||||
info
|
||||
end
|
||||
|
||||
#
|
||||
# Parse a "ret" TDS token
|
||||
#
|
||||
def mssql_parse_ret(data, info)
|
||||
ret = data.slice!(0,4).unpack('N')[0]
|
||||
info[:ret] = ret
|
||||
info
|
||||
end
|
||||
|
||||
#
|
||||
# Parse a "done" TDS token
|
||||
#
|
||||
def mssql_parse_done(data, info)
|
||||
status,cmd,rows = data.slice!(0,8).unpack('vvV')
|
||||
info[:done] = { :status => status, :cmd => cmd, :rows => rows }
|
||||
info
|
||||
end
|
||||
|
||||
#
|
||||
# Parse an "error" TDS token
|
||||
#
|
||||
def mssql_parse_error(data, info)
|
||||
len = data.slice!(0,2).unpack('v')[0]
|
||||
buff = data.slice!(0,len)
|
||||
|
||||
errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv')
|
||||
emsg = buff.slice!(0,elen * 2)
|
||||
emsg.gsub!("\x00", '')
|
||||
|
||||
info[:errors] << "SQL Server Error ##{errno} (State:#{state} Severity:#{sev}): #{emsg}"
|
||||
info
|
||||
end
|
||||
|
||||
#
|
||||
# Parse an "information" TDS token
|
||||
#
|
||||
def mssql_parse_info(data, info)
|
||||
len = data.slice!(0,2).unpack('v')[0]
|
||||
buff = data.slice!(0,len)
|
||||
|
||||
errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv')
|
||||
emsg = buff.slice!(0,elen * 2)
|
||||
emsg.gsub!("\x00", '')
|
||||
|
||||
info[:infos]||= []
|
||||
info[:infos] << "SQL Server Info ##{errno} (State:#{state} Severity:#{sev}): #{emsg}"
|
||||
info
|
||||
end
|
||||
|
||||
#
|
||||
# Parse a "login ack" TDS token
|
||||
#
|
||||
def mssql_parse_login_ack(data, info)
|
||||
len = data.slice!(0,2).unpack('v')[0]
|
||||
buff = data.slice!(0,len)
|
||||
info[:login_ack] = true
|
||||
end
|
||||
|
||||
#
|
||||
# Parse individual tokens from a TDS reply
|
||||
#
|
||||
def mssql_parse_reply(data, info)
|
||||
info[:errors] = []
|
||||
return if not data
|
||||
until data.empty?
|
||||
token = data.slice!(0,1).unpack('C')[0]
|
||||
case token
|
||||
when 0x81
|
||||
mssql_parse_tds_reply(data, info)
|
||||
when 0xd1
|
||||
mssql_parse_tds_row(data, info)
|
||||
when 0xe3
|
||||
mssql_parse_env(data, info)
|
||||
when 0x79
|
||||
mssql_parse_ret(data, info)
|
||||
when 0xfd, 0xfe, 0xff
|
||||
mssql_parse_done(data, info)
|
||||
when 0xad
|
||||
mssql_parse_login_ack(data, info)
|
||||
when 0xab
|
||||
mssql_parse_info(data, info)
|
||||
when 0xaa
|
||||
mssql_parse_error(data, info)
|
||||
when nil
|
||||
break
|
||||
else
|
||||
info[:errors] << "unsupported token: #{token}"
|
||||
end
|
||||
end
|
||||
info
|
||||
end
|
||||
|
||||
#
|
||||
# Parse a raw TDS reply from the server
|
||||
#
|
||||
def mssql_parse_tds_reply(data, info)
|
||||
info[:errors] ||= []
|
||||
info[:colinfos] ||= []
|
||||
info[:colnames] ||= []
|
||||
|
||||
# Parse out the columns
|
||||
cols = data.slice!(0,2).unpack('v')[0]
|
||||
0.upto(cols-1) do |col_idx|
|
||||
col = {}
|
||||
info[:colinfos][col_idx] = col
|
||||
|
||||
col[:utype] = data.slice!(0,2).unpack('v')[0]
|
||||
col[:flags] = data.slice!(0,2).unpack('v')[0]
|
||||
col[:type] = data.slice!(0,1).unpack('C')[0]
|
||||
|
||||
case col[:type]
|
||||
when 48
|
||||
col[:id] = :tinyint
|
||||
|
||||
when 52
|
||||
col[:id] = :smallint
|
||||
|
||||
when 56
|
||||
col[:id] = :rawint
|
||||
|
||||
when 61
|
||||
col[:id] = :datetime
|
||||
|
||||
when 34
|
||||
col[:id] = :image
|
||||
col[:max_size] = data.slice!(0,4).unpack('V')[0]
|
||||
col[:value_length] = data.slice!(0,2).unpack('v')[0]
|
||||
col[:value] = data.slice!(0, col[:value_length] * 2).gsub("\x00", '')
|
||||
|
||||
when 36
|
||||
col[:id] = :string
|
||||
|
||||
when 38
|
||||
col[:id] = :int
|
||||
col[:int_size] = data.slice!(0,1).unpack('C')[0]
|
||||
|
||||
when 127
|
||||
col[:id] = :bigint
|
||||
|
||||
when 165
|
||||
col[:id] = :hex
|
||||
col[:max_size] = data.slice!(0,2).unpack('v')[0]
|
||||
|
||||
when 173
|
||||
col[:id] = :hex # binary(2)
|
||||
col[:max_size] = data.slice!(0,2).unpack('v')[0]
|
||||
|
||||
when 231,175,167,239
|
||||
col[:id] = :string
|
||||
col[:max_size] = data.slice!(0,2).unpack('v')[0]
|
||||
col[:codepage] = data.slice!(0,2).unpack('v')[0]
|
||||
col[:cflags] = data.slice!(0,2).unpack('v')[0]
|
||||
col[:charset_id] = data.slice!(0,1).unpack('C')[0]
|
||||
|
||||
else
|
||||
col[:id] = :unknown
|
||||
end
|
||||
|
||||
col[:msg_len] = data.slice!(0,1).unpack('C')[0]
|
||||
|
||||
if(col[:msg_len] and col[:msg_len] > 0)
|
||||
col[:name] = data.slice!(0, col[:msg_len] * 2).gsub("\x00", '')
|
||||
end
|
||||
info[:colnames] << (col[:name] || 'NULL')
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Parse a single row of a TDS reply
|
||||
#
|
||||
def mssql_parse_tds_row(data, info)
|
||||
info[:rows] ||= []
|
||||
row = []
|
||||
|
||||
info[:colinfos].each do |col|
|
||||
|
||||
if(data.length == 0)
|
||||
row << "<EMPTY>"
|
||||
next
|
||||
end
|
||||
|
||||
case col[:id]
|
||||
when :hex
|
||||
str = ""
|
||||
len = data.slice!(0,2).unpack('v')[0]
|
||||
if(len > 0 and len < 65535)
|
||||
str << data.slice!(0,len)
|
||||
end
|
||||
row << str.unpack("H*")[0]
|
||||
|
||||
when :string
|
||||
str = ""
|
||||
len = data.slice!(0,2).unpack('v')[0]
|
||||
if(len > 0 and len < 65535)
|
||||
str << data.slice!(0,len)
|
||||
end
|
||||
row << str.gsub("\x00", '')
|
||||
|
||||
when :datetime
|
||||
row << data.slice!(0,8).unpack("H*")[0]
|
||||
|
||||
when :rawint
|
||||
row << data.slice!(0,4).unpack('V')[0]
|
||||
|
||||
when :bigint
|
||||
row << data.slice!(0,8).unpack("H*")[0]
|
||||
|
||||
when :smallint
|
||||
row << data.slice!(0, 2).unpack("v")[0]
|
||||
|
||||
when :smallint3
|
||||
row << [data.slice!(0, 3)].pack("Z4").unpack("V")[0]
|
||||
|
||||
when :tinyint
|
||||
row << data.slice!(0, 1).unpack("C")[0]
|
||||
|
||||
when :image
|
||||
str = ''
|
||||
len = data.slice!(0,1).unpack('C')[0]
|
||||
str = data.slice!(0,len) if (len and len > 0)
|
||||
row << str.unpack("H*")[0]
|
||||
|
||||
when :int
|
||||
len = data.slice!(0, 1).unpack("C")[0]
|
||||
raw = data.slice!(0, len) if (len and len > 0)
|
||||
|
||||
case len
|
||||
when 0,255
|
||||
row << ''
|
||||
when 1
|
||||
row << raw.unpack("C")[0]
|
||||
when 2
|
||||
row << raw.unpack('v')[0]
|
||||
when 4
|
||||
row << raw.unpack('V')[0]
|
||||
when 5
|
||||
row << raw.unpack('V')[0] # XXX: missing high byte
|
||||
when 8
|
||||
row << raw.unpack('VV')[0] # XXX: missing high dword
|
||||
else
|
||||
info[:errors] << "invalid integer size: #{len} #{data[0,16].unpack("H*")[0]}"
|
||||
end
|
||||
else
|
||||
info[:errors] << "unknown column type: #{col.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
info[:rows] << row
|
||||
info
|
||||
end
|
||||
|
||||
#
|
||||
#this method send a prelogin packet and check if encryption is off
|
||||
#
|
||||
def mssql_prelogin(enc_error=false)
|
||||
|
||||
pkt = ""
|
||||
pkt_hdr = ""
|
||||
pkt_data_token = ""
|
||||
pkt_data = ""
|
||||
|
||||
|
||||
pkt_hdr = [
|
||||
TYPE_PRE_LOGIN_MESSAGE, #type
|
||||
STATUS_END_OF_MESSAGE, #status
|
||||
0x0000, #length
|
||||
0x0000, # SPID
|
||||
0x00, # PacketID
|
||||
0x00 #Window
|
||||
]
|
||||
|
||||
version = [0x55010008,0x0000].pack("Vv")
|
||||
encryption = ENCRYPT_NOT_SUP # off
|
||||
instoptdata = "MSSQLServer\0"
|
||||
|
||||
threadid = "\0\0" + Rex::Text.rand_text(2)
|
||||
|
||||
idx = 21 # size of pkt_data_token
|
||||
pkt_data_token << [
|
||||
0x00, # Token 0 type Version
|
||||
idx , # VersionOffset
|
||||
version.length, # VersionLength
|
||||
|
||||
0x01, # Token 1 type Encryption
|
||||
idx = idx + version.length, # EncryptionOffset
|
||||
0x01, # EncryptionLength
|
||||
|
||||
0x02, # Token 2 type InstOpt
|
||||
idx = idx + 1, # InstOptOffset
|
||||
instoptdata.length, # InstOptLength
|
||||
|
||||
0x03, # Token 3 type Threadid
|
||||
idx + instoptdata.length, # ThreadIdOffset
|
||||
0x04, # ThreadIdLength
|
||||
|
||||
0xFF
|
||||
].pack("CnnCnnCnnCnnC")
|
||||
|
||||
pkt_data << pkt_data_token
|
||||
pkt_data << version
|
||||
pkt_data << encryption
|
||||
pkt_data << instoptdata
|
||||
pkt_data << threadid
|
||||
|
||||
pkt_hdr[2] = pkt_data.length + 8
|
||||
|
||||
pkt = pkt_hdr.pack("CCnnCC") + pkt_data
|
||||
|
||||
resp = mssql_send_recv(pkt)
|
||||
|
||||
idx = 0
|
||||
|
||||
while resp and resp[0,1] != "\xff" and resp.length > 5
|
||||
token = resp.slice!(0,5)
|
||||
token = token.unpack("Cnn")
|
||||
idx -= 5
|
||||
if token[0] == 0x01
|
||||
|
||||
idx += token[1]
|
||||
break
|
||||
end
|
||||
end
|
||||
if idx > 0
|
||||
encryption_mode = resp[idx,1].unpack("C")[0]
|
||||
else
|
||||
#force to ENCRYPT_NOT_SUP and hope for the best
|
||||
encryption_mode = ENCRYPT_NOT_SUP
|
||||
end
|
||||
|
||||
if encryption_mode != ENCRYPT_NOT_SUP and enc_error
|
||||
raise RuntimeError,"Encryption is not supported"
|
||||
end
|
||||
encryption_mode
|
||||
end
|
||||
|
||||
#
|
||||
# Send and receive using TDS
|
||||
#
|
||||
def mssql_send_recv(req, timeout=15, check_status = true)
|
||||
sock.put(req)
|
||||
|
||||
# Read the 8 byte header to get the length and status
|
||||
# Read the length to get the data
|
||||
# If the status is 0, read another header and more data
|
||||
|
||||
done = false
|
||||
resp = ""
|
||||
|
||||
while(not done)
|
||||
head = sock.get_once(8, timeout)
|
||||
if !(head and head.length == 8)
|
||||
return false
|
||||
end
|
||||
|
||||
# Is this the last buffer?
|
||||
if(head[1,1] == "\x01" or not check_status )
|
||||
done = true
|
||||
end
|
||||
|
||||
# Grab this block's length
|
||||
rlen = head[2,2].unpack('n')[0] - 8
|
||||
|
||||
while(rlen > 0)
|
||||
buff = sock.get_once(rlen, timeout)
|
||||
return if not buff
|
||||
resp << buff
|
||||
rlen -= buff.length
|
||||
end
|
||||
end
|
||||
|
||||
resp
|
||||
end
|
||||
|
||||
#
|
||||
# Encrypt a password according to the TDS protocol (encode)
|
||||
#
|
||||
def mssql_tds_encrypt(pass)
|
||||
# Convert to unicode, swap 4 bits both ways, xor with 0xa5
|
||||
Rex::Text.to_unicode(pass).unpack('C*').map {|c| (((c & 0x0f) << 4) + ((c & 0xf0) >> 4)) ^ 0xa5 }.pack("C*")
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def windows_authentication
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def use_ntlm2_session
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def use_ntlmv2
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def send_lm
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def send_ntlm
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def send_spn
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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] )
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue