Merge feature/recog into post-electro master for this PR
commit
6d92d701d7
|
@ -15,8 +15,6 @@ Gemfile.local.lock
|
|||
config/database.yml
|
||||
# simplecov coverage data
|
||||
coverage
|
||||
data/meterpreter/ext_server_pivot.x86.dll
|
||||
data/meterpreter/ext_server_pivot.x64.dll
|
||||
doc/
|
||||
external/source/meterpreter/java/bin
|
||||
external/source/meterpreter/java/build
|
||||
|
@ -50,6 +48,28 @@ tags
|
|||
*.opensdf
|
||||
*.user
|
||||
|
||||
# Rails log directory
|
||||
/log
|
||||
|
||||
# ignore release/debug folders for exploits
|
||||
external/source/exploits/**/Debug
|
||||
external/source/exploits/**/Release
|
||||
|
||||
# Avoid checking in Meterpreter binaries. These are supplied upstream by
|
||||
# the meterpreter_bins gem.
|
||||
data/meterpreter/elevator.*.dll
|
||||
data/meterpreter/ext_server_espia.*.dll
|
||||
data/meterpreter/ext_server_extapi.*.dll
|
||||
data/meterpreter/ext_server_incognito.*.dll
|
||||
data/meterpreter/ext_server_kiwi.*.dll
|
||||
data/meterpreter/ext_server_lanattacks.*.dll
|
||||
data/meterpreter/ext_server_mimikatz.*.dll
|
||||
data/meterpreter/ext_server_priv.*.dll
|
||||
data/meterpreter/ext_server_stdapi.*.dll
|
||||
data/meterpreter/metsrv.*.dll
|
||||
data/meterpreter/screenshot.*.dll
|
||||
|
||||
# Avoid checking in Meterpreter libs that are built from
|
||||
# private source. If you're interested in this functionality,
|
||||
# check out Metasploit Pro: http://metasploit.com/download
|
||||
data/meterpreter/ext_server_pivot.*.dll
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
# This list was intially created by analyzing the last three months (51
|
||||
# modules) committed to Metasploit Framework. Many, many older modules
|
||||
# will have offenses, but this should at least provide a baseline for
|
||||
# new modules.
|
||||
#
|
||||
# Updates to this file should include a 'Description' parameter for any
|
||||
# explaination needed.
|
||||
|
||||
# inherit_from: .rubocop_todo.yml
|
||||
|
||||
Style/ClassLength:
|
||||
Description: 'Most Metasploit modules are quite large. This is ok.'
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- 'modules/**/*'
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: true
|
||||
Description: 'Most Metasploit modules do not have class documentation.'
|
||||
Exclude:
|
||||
- 'modules/**/*'
|
||||
|
||||
Style/Encoding:
|
||||
Enabled: true
|
||||
Description: 'We prefer binary to UTF-8.'
|
||||
EnforcedStyle: 'when_needed'
|
||||
|
||||
Style/LineLength:
|
||||
Description: >-
|
||||
Metasploit modules often pattern match against very
|
||||
long strings when identifying targets.
|
||||
Enabled: true
|
||||
Max: 180
|
||||
|
||||
Style/MethodLength:
|
||||
Enabled: true
|
||||
Description: >-
|
||||
While the style guide suggests 10 lines, exploit definitions
|
||||
often exceed 200 lines.
|
||||
Max: 300
|
||||
|
||||
Style/NumericLiterals:
|
||||
Enabled: false
|
||||
Description: 'This often hurts readability for exploit-ish code.'
|
||||
|
||||
Style/SpaceInsideBrackets:
|
||||
Enabled: false
|
||||
Description: 'Until module template are final, most modules will fail this.'
|
||||
|
||||
Style/StringLiterals:
|
||||
Enabled: false
|
||||
Description: 'Single vs double quote fights are largely unproductive.'
|
||||
|
||||
Style/WordArray:
|
||||
Enabled: false
|
||||
Description: 'Metasploit prefers consistent use of []'
|
|
@ -3,5 +3,8 @@
|
|||
--exclude \.ut\.rb/
|
||||
--exclude \.ts\.rb/
|
||||
--files CONTRIBUTING.md,COPYING,HACKING,LICENSE
|
||||
app/**/*.rb
|
||||
lib/msf/**/*.rb
|
||||
lib/metasploit/**/*.rb
|
||||
lib/rex/**/*.rb
|
||||
plugins/**/*.rb
|
||||
|
|
|
@ -33,6 +33,7 @@ and Metasploit's [Common Coding Mistakes](https://github.com/rapid7/metasploit-f
|
|||
## Code Contributions
|
||||
|
||||
* **Do** stick to the [Ruby style guide](https://github.com/bbatsov/ruby-style-guide).
|
||||
* *Do* get [Rubocop](https://rubygems.org/search?query=rubocop) relatively quiet against the code you are adding or modifying.
|
||||
* **Do** follow the [50/72 rule](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for Git commit messages.
|
||||
* **Do** create a [topic branch](http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) to work on instead of working directly on `master`.
|
||||
|
||||
|
|
89
Gemfile
89
Gemfile
|
@ -1,73 +1,54 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
# Need 3+ for ActiveSupport::Concern
|
||||
gem 'activesupport', '>= 3.0.0', '< 4.0.0'
|
||||
# Needed for some admin modules (cfme_manageiq_evm_pass_reset.rb)
|
||||
gem 'bcrypt'
|
||||
# Needed for some admin modules (scrutinizer_add_user.rb)
|
||||
gem 'json'
|
||||
# Needed by msfgui and other rpc components
|
||||
gem 'msgpack'
|
||||
# Needed by anemone crawler
|
||||
gem 'nokogiri'
|
||||
# Needed by db.rb and Msf::Exploit::Capture
|
||||
gem 'packetfu', '1.1.9'
|
||||
# Needed by JSObfu
|
||||
gem 'rkelly-remix', '0.0.6'
|
||||
# Needed by anemone crawler
|
||||
gem 'robots'
|
||||
|
||||
# Needed for service fingerprinting (Recog)
|
||||
gem 'recog', :git => 'git@github.com:rapid7/recog.git'
|
||||
|
||||
# Needed for some post modules
|
||||
gem 'sqlite3'
|
||||
# Add default group gems to `metasploit-framework.gemspec`:
|
||||
# spec.add_runtime_dependency '<name>', [<version requirements>]
|
||||
gemspec
|
||||
|
||||
group :db do
|
||||
# Needed for Msf::DbManager
|
||||
gem 'activerecord', '>= 3.0.0', '< 4.0.0'
|
||||
|
||||
# Database models shared between framework and Pro (depends on hmoore-r7 branch until merged)
|
||||
# Unfortunately, MDM is broken after 0.17.0 in master right now, so waiting on that merge...
|
||||
|
||||
gem 'metasploit_data_models', '= 0.17.2'
|
||||
# gem 'metasploit_data_models', :git => 'git@github.com:hmoore-r7/metasploit_data_models.git'
|
||||
|
||||
# Metasploit::Credential database models
|
||||
gem 'metasploit-credential', '>= 0.8.6', '< 0.9'
|
||||
# Database models shared between framework and Pro.
|
||||
# gem 'metasploit_data_models', '~> 0.19'
|
||||
gem 'metasploit_data_models', :git => 'git@github.com:hmoore-r7/metasploit_data_models.git'
|
||||
# Needed for module caching in Mdm::ModuleDetails
|
||||
gem 'pg', '>= 0.11'
|
||||
end
|
||||
|
||||
group :development do
|
||||
# Markdown formatting for yard
|
||||
gem 'redcarpet'
|
||||
# generating documentation
|
||||
gem 'yard'
|
||||
# for development and testing purposes
|
||||
gem 'pry'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
# supplies factories for producing model instance for specs
|
||||
# Version 4.1.0 or newer is needed to support generate calls without the
|
||||
# 'FactoryGirl.' in factory definitions syntax.
|
||||
gem 'factory_girl', '>= 4.1.0'
|
||||
# automatically include factories from spec/factories
|
||||
gem 'factory_girl_rails'
|
||||
# Make rspec output shorter and more useful
|
||||
gem 'fivemat', '1.2.1'
|
||||
# running documentation generation tasks and rspec tasks
|
||||
gem 'rake', '>= 10.0.0'
|
||||
# testing framework
|
||||
gem 'rspec', '>= 2.12', '< 3.0.0'
|
||||
# Define `rake spec`. Must be in development AND test so that its available by default as a rake test when the
|
||||
# environment is development
|
||||
gem 'rspec-rails' , '>= 2.12', '< 3.0.0'
|
||||
end
|
||||
|
||||
group :pcap do
|
||||
gem 'network_interface', '~> 0.0.1'
|
||||
# For sniffer and raw socket modules
|
||||
gem 'pcaprub'
|
||||
end
|
||||
|
||||
group :development do
|
||||
# Markdown formatting for yard
|
||||
gem 'redcarpet'
|
||||
# generating documentation
|
||||
gem 'yard'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
# supplies factories for producing model instance for specs
|
||||
# Version 4.1.0 or newer is needed to support generate calls without the
|
||||
# 'FactoryGirl.' in factory definitions syntax.
|
||||
gem 'factory_girl', '>= 4.1.0'
|
||||
# Make rspec output shorter and more useful
|
||||
gem 'fivemat', '1.2.1'
|
||||
# running documentation generation tasks and rspec tasks
|
||||
gem 'rake', '>= 10.0.0'
|
||||
end
|
||||
|
||||
group :test do
|
||||
# Removes records from database created during tests. Can't use rspec-rails'
|
||||
# transactional fixtures because multiple connections are in use so
|
||||
# transactions won't work.
|
||||
gem 'database_cleaner'
|
||||
# testing framework
|
||||
gem 'rspec', '>= 2.12'
|
||||
gem 'shoulda-matchers'
|
||||
# code coverage for tests
|
||||
# any version newer than 0.5.4 gives an Encoding error when trying to read the source files.
|
||||
|
|
|
@ -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
|
||||
|
|
133
Gemfile.lock
133
Gemfile.lock
|
@ -1,13 +1,49 @@
|
|||
GIT
|
||||
remote: git@github.com:rapid7/recog.git
|
||||
revision: 9c2983de2e2ebbeb98c2211b5be95ab4099479bc
|
||||
remote: git@github.com:hmoore-r7/metasploit_data_models.git
|
||||
revision: 73846d7fb08817e44ec09684d7af1cd2372f6799
|
||||
specs:
|
||||
recog (0.01)
|
||||
metasploit_data_models (0.19.7)
|
||||
activerecord (>= 3.2.13, < 4.0.0)
|
||||
activesupport
|
||||
arel-helpers
|
||||
metasploit-concern (~> 0.1.0)
|
||||
metasploit-model (~> 0.26.1)
|
||||
pg
|
||||
recog
|
||||
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (4.10.1.pre.dev)
|
||||
actionpack (< 4.0.0)
|
||||
activesupport (>= 3.0.0, < 4.0.0)
|
||||
bcrypt
|
||||
json
|
||||
metasploit-model (~> 0.26.1)
|
||||
meterpreter_bins (= 0.0.6)
|
||||
msgpack
|
||||
nokogiri
|
||||
packetfu (= 1.1.9)
|
||||
railties
|
||||
rkelly-remix (= 0.0.6)
|
||||
robots
|
||||
rubyzip (~> 1.1)
|
||||
sqlite3
|
||||
tzinfo
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionpack (3.2.19)
|
||||
activemodel (= 3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
builder (~> 3.0.0)
|
||||
erubis (~> 2.7.0)
|
||||
journey (~> 1.0.4)
|
||||
rack (~> 1.4.5)
|
||||
rack-cache (~> 1.2)
|
||||
rack-test (~> 0.6.1)
|
||||
sprockets (~> 2.2.1)
|
||||
activemodel (3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
builder (~> 3.0.0)
|
||||
|
@ -20,29 +56,68 @@ GEM
|
|||
i18n (~> 0.6, >= 0.6.4)
|
||||
multi_json (~> 1.0)
|
||||
arel (3.0.3)
|
||||
arel-helpers (2.0.1)
|
||||
activerecord (>= 3.1.0, < 5)
|
||||
bcrypt (3.1.7)
|
||||
builder (3.0.4)
|
||||
database_cleaner (1.1.1)
|
||||
coderay (1.1.0)
|
||||
diff-lcs (1.2.4)
|
||||
erubis (2.7.0)
|
||||
factory_girl (4.2.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (4.2.1)
|
||||
factory_girl (~> 4.2.0)
|
||||
railties (>= 3.0.0)
|
||||
fivemat (1.2.1)
|
||||
hike (1.2.3)
|
||||
i18n (0.6.9)
|
||||
json (1.8.0)
|
||||
metasploit_data_models (0.17.0)
|
||||
activerecord (>= 3.2.13)
|
||||
activesupport
|
||||
journey (1.0.4)
|
||||
json (1.8.1)
|
||||
metasploit-concern (0.1.1)
|
||||
activesupport (~> 3.0, >= 3.0.0)
|
||||
metasploit-credential (0.8.9)
|
||||
metasploit-concern (~> 0.1.0)
|
||||
metasploit-model (~> 0.26.1)
|
||||
metasploit_data_models (~> 0.19.4)
|
||||
pg
|
||||
rubyntlm
|
||||
rubyzip (~> 1.1)
|
||||
metasploit-model (0.26.1)
|
||||
activesupport
|
||||
meterpreter_bins (0.0.6)
|
||||
method_source (0.8.2)
|
||||
mini_portile (0.6.0)
|
||||
msgpack (0.5.5)
|
||||
msgpack (0.5.8)
|
||||
multi_json (1.0.4)
|
||||
network_interface (0.0.1)
|
||||
nokogiri (1.6.2.1)
|
||||
nokogiri (1.6.3.1)
|
||||
mini_portile (= 0.6.0)
|
||||
packetfu (1.1.9)
|
||||
pcaprub (0.11.3)
|
||||
pg (0.17.1)
|
||||
pry (0.10.0)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
rack (1.4.5)
|
||||
rack-cache (1.2)
|
||||
rack (>= 0.4)
|
||||
rack-ssl (1.3.4)
|
||||
rack
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
railties (3.2.19)
|
||||
actionpack (= 3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
rack-ssl (~> 1.3.2)
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
rake (10.1.0)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
recog (0.01)
|
||||
nokogiri
|
||||
redcarpet (3.0.0)
|
||||
rkelly-remix (0.0.6)
|
||||
robots (0.10.1)
|
||||
|
@ -54,13 +129,31 @@ GEM
|
|||
rspec-expectations (2.14.2)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rspec-mocks (2.14.3)
|
||||
rspec-rails (2.14.2)
|
||||
actionpack (>= 3.0)
|
||||
activemodel (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rubyntlm (0.4.0)
|
||||
rubyzip (1.1.6)
|
||||
shoulda-matchers (2.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
simplecov (0.5.4)
|
||||
multi_json (~> 1.0.3)
|
||||
simplecov-html (~> 0.5.3)
|
||||
simplecov-html (0.5.3)
|
||||
slop (3.6.0)
|
||||
sprockets (2.2.2)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
sqlite3 (1.3.9)
|
||||
thor (0.19.1)
|
||||
tilt (1.4.1)
|
||||
timecop (0.6.3)
|
||||
tzinfo (0.3.39)
|
||||
yard (0.8.7)
|
||||
|
@ -70,27 +163,21 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
activerecord (>= 3.0.0, < 4.0.0)
|
||||
activesupport (>= 3.0.0, < 4.0.0)
|
||||
bcrypt
|
||||
database_cleaner
|
||||
factory_girl (>= 4.1.0)
|
||||
factory_girl_rails
|
||||
fivemat (= 1.2.1)
|
||||
json
|
||||
metasploit_data_models (= 0.17.0)
|
||||
msgpack
|
||||
metasploit-credential (>= 0.8.6, < 0.9)
|
||||
metasploit-framework!
|
||||
metasploit_data_models!
|
||||
network_interface (~> 0.0.1)
|
||||
nokogiri
|
||||
packetfu (= 1.1.9)
|
||||
pcaprub
|
||||
pg (>= 0.11)
|
||||
pry
|
||||
rake (>= 10.0.0)
|
||||
recog!
|
||||
redcarpet
|
||||
rkelly-remix (= 0.0.6)
|
||||
robots
|
||||
rspec (>= 2.12)
|
||||
rspec (>= 2.12, < 3.0.0)
|
||||
rspec-rails (>= 2.12, < 3.0.0)
|
||||
shoulda-matchers
|
||||
simplecov (= 0.5.4)
|
||||
sqlite3
|
||||
timecop
|
||||
yard
|
||||
|
|
2
HACKING
2
HACKING
|
@ -10,7 +10,7 @@ CONTRIBUTING.md
|
|||
in the same directory as this file, and to a lesser extent:
|
||||
|
||||
The Metasploit Development Environment
|
||||
https://github.com/rapid7/metasploit-framework/wiki/Metasploit-Development-Environment
|
||||
https://github.com/rapid7/metasploit-framework/wiki/Setting-Up-a-Metasploit-Development-Environment
|
||||
|
||||
Common Coding Mistakes
|
||||
https://github.com/rapid7/metasploit-framework/wiki/Common-Metasploit-Module-Coding-Mistakes
|
||||
|
|
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,58 @@
|
|||
require 'rails'
|
||||
require File.expand_path('../boot', __FILE__)
|
||||
|
||||
all_environments = [
|
||||
:development,
|
||||
:production,
|
||||
:test
|
||||
]
|
||||
|
||||
Bundler.require(
|
||||
*Rails.groups(
|
||||
db: all_environments,
|
||||
pcap: all_environments
|
||||
)
|
||||
)
|
||||
|
||||
#
|
||||
# Railties
|
||||
#
|
||||
|
||||
# For compatibility with jquery-rails (and other engines that need action_view) in pro
|
||||
require 'action_view/railtie'
|
||||
|
||||
#
|
||||
# Project
|
||||
#
|
||||
|
||||
require 'metasploit/framework/common_engine'
|
||||
require 'msf/base/config'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
class Application < Rails::Application
|
||||
include Metasploit::Framework::CommonEngine
|
||||
|
||||
environment_database_yaml = ENV['MSF_DATABASE_CONFIG']
|
||||
|
||||
if environment_database_yaml
|
||||
# DO NOT check if the path exists: if the environment variable is set, then the user meant to use this path
|
||||
# and if it doesn't exist then an error should occur so the user knows the environment variable points to a
|
||||
# non-existent file.
|
||||
config.paths['config/database'] = environment_database_yaml
|
||||
else
|
||||
user_config_root = Pathname.new(Msf::Config.get_config_root)
|
||||
user_database_yaml = user_config_root.join('database.yml')
|
||||
|
||||
# DO check if the path exists as in test environments there may be no config root, in which case the normal
|
||||
# rails location, `config/database.yml`, should contain the database config.
|
||||
if user_database_yaml.exist?
|
||||
config.paths['config/database'] = [user_database_yaml.to_path]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Silence warnings about this defaulting to true
|
||||
I18n.enforce_available_locales = true
|
|
@ -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,5 @@
|
|||
# Load the rails application
|
||||
require File.expand_path('../application', __FILE__)
|
||||
|
||||
# Initialize the rails application
|
||||
Metasploit::Framework::Application.initialize!
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -6,10 +6,10 @@
|
|||
##
|
||||
# General
|
||||
##
|
||||
define("TLV_TYPE_HANDLE", TLV_META_TYPE_UINT | 600);
|
||||
define("TLV_TYPE_HANDLE", TLV_META_TYPE_QWORD | 600);
|
||||
define("TLV_TYPE_INHERIT", TLV_META_TYPE_BOOL | 601);
|
||||
define("TLV_TYPE_PROCESS_HANDLE", TLV_META_TYPE_UINT | 630);
|
||||
define("TLV_TYPE_THREAD_HANDLE", TLV_META_TYPE_UINT | 631);
|
||||
define("TLV_TYPE_PROCESS_HANDLE", TLV_META_TYPE_QWORD | 630);
|
||||
define("TLV_TYPE_THREAD_HANDLE", TLV_META_TYPE_QWORD | 631);
|
||||
|
||||
##
|
||||
# Fs
|
||||
|
@ -65,7 +65,7 @@ define("PROCESS_EXECUTE_FLAG_SUSPENDED", (1 << 2));
|
|||
define("PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN", (1 << 3));
|
||||
|
||||
# Registry
|
||||
define("TLV_TYPE_HKEY", TLV_META_TYPE_UINT | 1000);
|
||||
define("TLV_TYPE_HKEY", TLV_META_TYPE_QWORD | 1000);
|
||||
define("TLV_TYPE_ROOT_KEY", TLV_TYPE_HKEY);
|
||||
define("TLV_TYPE_BASE_KEY", TLV_META_TYPE_STRING | 1001);
|
||||
define("TLV_TYPE_PERMISSION", TLV_META_TYPE_UINT | 1002);
|
||||
|
@ -90,12 +90,12 @@ define("TLV_TYPE_ENV_GROUP", TLV_META_TYPE_GROUP | 1102);
|
|||
define("DELETE_KEY_FLAG_RECURSIVE", (1 << 0));
|
||||
|
||||
# Process
|
||||
define("TLV_TYPE_BASE_ADDRESS", TLV_META_TYPE_UINT | 2000);
|
||||
define("TLV_TYPE_BASE_ADDRESS", TLV_META_TYPE_QWORD | 2000);
|
||||
define("TLV_TYPE_ALLOCATION_TYPE", TLV_META_TYPE_UINT | 2001);
|
||||
define("TLV_TYPE_PROTECTION", TLV_META_TYPE_UINT | 2002);
|
||||
define("TLV_TYPE_PROCESS_PERMS", TLV_META_TYPE_UINT | 2003);
|
||||
define("TLV_TYPE_PROCESS_MEMORY", TLV_META_TYPE_RAW | 2004);
|
||||
define("TLV_TYPE_ALLOC_BASE_ADDRESS", TLV_META_TYPE_UINT | 2005);
|
||||
define("TLV_TYPE_ALLOC_BASE_ADDRESS", TLV_META_TYPE_QWORD | 2005);
|
||||
define("TLV_TYPE_MEMORY_STATE", TLV_META_TYPE_UINT | 2006);
|
||||
define("TLV_TYPE_MEMORY_TYPE", TLV_META_TYPE_UINT | 2007);
|
||||
define("TLV_TYPE_ALLOC_PROTECTION", TLV_META_TYPE_UINT | 2008);
|
||||
|
@ -109,16 +109,16 @@ define("TLV_TYPE_PROCESS_ARGUMENTS", TLV_META_TYPE_STRING | 2305);
|
|||
define("TLV_TYPE_IMAGE_FILE", TLV_META_TYPE_STRING | 2400);
|
||||
define("TLV_TYPE_IMAGE_FILE_PATH", TLV_META_TYPE_STRING | 2401);
|
||||
define("TLV_TYPE_PROCEDURE_NAME", TLV_META_TYPE_STRING | 2402);
|
||||
define("TLV_TYPE_PROCEDURE_ADDRESS", TLV_META_TYPE_UINT | 2403);
|
||||
define("TLV_TYPE_IMAGE_BASE", TLV_META_TYPE_UINT | 2404);
|
||||
define("TLV_TYPE_PROCEDURE_ADDRESS", TLV_META_TYPE_QWORD | 2403);
|
||||
define("TLV_TYPE_IMAGE_BASE", TLV_META_TYPE_QWORD | 2404);
|
||||
define("TLV_TYPE_IMAGE_GROUP", TLV_META_TYPE_GROUP | 2405);
|
||||
define("TLV_TYPE_IMAGE_NAME", TLV_META_TYPE_STRING | 2406);
|
||||
|
||||
define("TLV_TYPE_THREAD_ID", TLV_META_TYPE_UINT | 2500);
|
||||
define("TLV_TYPE_THREAD_PERMS", TLV_META_TYPE_UINT | 2502);
|
||||
define("TLV_TYPE_EXIT_CODE", TLV_META_TYPE_UINT | 2510);
|
||||
define("TLV_TYPE_ENTRY_POINT", TLV_META_TYPE_UINT | 2511);
|
||||
define("TLV_TYPE_ENTRY_PARAMETER", TLV_META_TYPE_UINT | 2512);
|
||||
define("TLV_TYPE_ENTRY_POINT", TLV_META_TYPE_QWORD | 2511);
|
||||
define("TLV_TYPE_ENTRY_PARAMETER", TLV_META_TYPE_QWORD | 2512);
|
||||
define("TLV_TYPE_CREATION_FLAGS", TLV_META_TYPE_UINT | 2513);
|
||||
|
||||
define("TLV_TYPE_REGISTER_NAME", TLV_META_TYPE_STRING | 2540);
|
||||
|
@ -137,7 +137,7 @@ define("TLV_TYPE_DESKTOP", TLV_META_TYPE_STRING | 3002);
|
|||
# Event Log
|
||||
##
|
||||
define("TLV_TYPE_EVENT_SOURCENAME", TLV_META_TYPE_STRING | 4000);
|
||||
define("TLV_TYPE_EVENT_HANDLE", TLV_META_TYPE_UINT | 4001);
|
||||
define("TLV_TYPE_EVENT_HANDLE", TLV_META_TYPE_QWORD | 4001);
|
||||
define("TLV_TYPE_EVENT_NUMRECORDS", TLV_META_TYPE_UINT | 4002);
|
||||
|
||||
define("TLV_TYPE_EVENT_READFLAGS", TLV_META_TYPE_UINT | 4003);
|
||||
|
|
|
@ -252,6 +252,7 @@ TLV_META_TYPE_STRING = (1 << 16)
|
|||
TLV_META_TYPE_UINT = (1 << 17)
|
||||
TLV_META_TYPE_RAW = (1 << 18)
|
||||
TLV_META_TYPE_BOOL = (1 << 19)
|
||||
TLV_META_TYPE_QWORD = (1 << 20)
|
||||
TLV_META_TYPE_COMPRESSED = (1 << 29)
|
||||
TLV_META_TYPE_GROUP = (1 << 30)
|
||||
TLV_META_TYPE_COMPLEX = (1 << 31)
|
||||
|
@ -284,10 +285,10 @@ TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54
|
|||
##
|
||||
# General
|
||||
##
|
||||
TLV_TYPE_HANDLE = TLV_META_TYPE_UINT | 600
|
||||
TLV_TYPE_HANDLE = TLV_META_TYPE_QWORD | 600
|
||||
TLV_TYPE_INHERIT = TLV_META_TYPE_BOOL | 601
|
||||
TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_UINT | 630
|
||||
TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_UINT | 631
|
||||
TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_QWORD | 630
|
||||
TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_QWORD | 631
|
||||
|
||||
##
|
||||
# Fs
|
||||
|
@ -346,7 +347,7 @@ TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530
|
|||
##
|
||||
# Registry
|
||||
##
|
||||
TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000
|
||||
TLV_TYPE_HKEY = TLV_META_TYPE_QWORD | 1000
|
||||
TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY
|
||||
TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001
|
||||
TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002
|
||||
|
@ -376,12 +377,12 @@ DELETE_KEY_FLAG_RECURSIVE = (1 << 0)
|
|||
##
|
||||
# Process
|
||||
##
|
||||
TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_UINT | 2000
|
||||
TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2000
|
||||
TLV_TYPE_ALLOCATION_TYPE = TLV_META_TYPE_UINT | 2001
|
||||
TLV_TYPE_PROTECTION = TLV_META_TYPE_UINT | 2002
|
||||
TLV_TYPE_PROCESS_PERMS = TLV_META_TYPE_UINT | 2003
|
||||
TLV_TYPE_PROCESS_MEMORY = TLV_META_TYPE_RAW | 2004
|
||||
TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_UINT | 2005
|
||||
TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2005
|
||||
TLV_TYPE_MEMORY_STATE = TLV_META_TYPE_UINT | 2006
|
||||
TLV_TYPE_MEMORY_TYPE = TLV_META_TYPE_UINT | 2007
|
||||
TLV_TYPE_ALLOC_PROTECTION = TLV_META_TYPE_UINT | 2008
|
||||
|
@ -397,16 +398,16 @@ TLV_TYPE_PARENT_PID = TLV_META_TYPE_UINT | 2307
|
|||
TLV_TYPE_IMAGE_FILE = TLV_META_TYPE_STRING | 2400
|
||||
TLV_TYPE_IMAGE_FILE_PATH = TLV_META_TYPE_STRING | 2401
|
||||
TLV_TYPE_PROCEDURE_NAME = TLV_META_TYPE_STRING | 2402
|
||||
TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_UINT | 2403
|
||||
TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_UINT | 2404
|
||||
TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_QWORD | 2403
|
||||
TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_QWORD | 2404
|
||||
TLV_TYPE_IMAGE_GROUP = TLV_META_TYPE_GROUP | 2405
|
||||
TLV_TYPE_IMAGE_NAME = TLV_META_TYPE_STRING | 2406
|
||||
|
||||
TLV_TYPE_THREAD_ID = TLV_META_TYPE_UINT | 2500
|
||||
TLV_TYPE_THREAD_PERMS = TLV_META_TYPE_UINT | 2502
|
||||
TLV_TYPE_EXIT_CODE = TLV_META_TYPE_UINT | 2510
|
||||
TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_UINT | 2511
|
||||
TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_UINT | 2512
|
||||
TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_QWORD | 2511
|
||||
TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_QWORD | 2512
|
||||
TLV_TYPE_CREATION_FLAGS = TLV_META_TYPE_UINT | 2513
|
||||
|
||||
TLV_TYPE_REGISTER_NAME = TLV_META_TYPE_STRING | 2540
|
||||
|
@ -425,7 +426,7 @@ TLV_TYPE_DESKTOP = TLV_META_TYPE_STRING | 3002
|
|||
# Event Log
|
||||
##
|
||||
TLV_TYPE_EVENT_SOURCENAME = TLV_META_TYPE_STRING | 4000
|
||||
TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_UINT | 4001
|
||||
TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_QWORD | 4001
|
||||
TLV_TYPE_EVENT_NUMRECORDS = TLV_META_TYPE_UINT | 4002
|
||||
|
||||
TLV_TYPE_EVENT_READFLAGS = TLV_META_TYPE_UINT | 4003
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -125,6 +125,7 @@ define("TLV_META_TYPE_STRING", (1 << 16));
|
|||
define("TLV_META_TYPE_UINT", (1 << 17));
|
||||
define("TLV_META_TYPE_RAW", (1 << 18));
|
||||
define("TLV_META_TYPE_BOOL", (1 << 19));
|
||||
define("TLV_META_TYPE_QWORD", (1 << 20));
|
||||
define("TLV_META_TYPE_COMPRESSED", (1 << 29));
|
||||
define("TLV_META_TYPE_GROUP", (1 << 30));
|
||||
define("TLV_META_TYPE_COMPLEX", (1 << 31));
|
||||
|
@ -655,6 +656,11 @@ function tlv_pack($tlv) {
|
|||
if (($tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING) {
|
||||
$ret = pack("NNa*", 8 + strlen($tlv['value'])+1, $tlv['type'], $tlv['value'] . "\0");
|
||||
}
|
||||
elseif (($tlv['type'] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD) {
|
||||
$hi = ($tlv['value'] >> 32) & 0xFFFFFFFF;
|
||||
$lo = $tlv['value'] & 0xFFFFFFFF;
|
||||
$ret = pack("NNNN", 8 + 8, $tlv['type'], $hi, $lo);
|
||||
}
|
||||
elseif (($tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) {
|
||||
$ret = pack("NNN", 8 + 4, $tlv['type'], $tlv['value']);
|
||||
}
|
||||
|
@ -693,6 +699,10 @@ function tlv_unpack($raw_tlv) {
|
|||
elseif (($type & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) {
|
||||
$tlv = unpack("Nlen/Ntype/Nvalue", substr($raw_tlv, 0, $tlv['len']));
|
||||
}
|
||||
elseif (($type & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD) {
|
||||
$tlv = unpack("Nlen/Ntype/Nhi/Nlo", substr($raw_tlv, 0, $tlv['len']));
|
||||
$tlv['value'] = $tlv['hi'] << 32 | $tlv['lo'];
|
||||
}
|
||||
elseif (($type & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL) {
|
||||
$tlv = unpack("Nlen/Ntype/cvalue", substr($raw_tlv, 0, $tlv['len']));
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ TLV_META_TYPE_STRING = (1 << 16)
|
|||
TLV_META_TYPE_UINT = (1 << 17)
|
||||
TLV_META_TYPE_RAW = (1 << 18)
|
||||
TLV_META_TYPE_BOOL = (1 << 19)
|
||||
TLV_META_TYPE_QWORD = (1 << 20)
|
||||
TLV_META_TYPE_COMPRESSED = (1 << 29)
|
||||
TLV_META_TYPE_GROUP = (1 << 30)
|
||||
TLV_META_TYPE_COMPLEX = (1 << 31)
|
||||
|
@ -150,6 +151,8 @@ def packet_enum_tlvs(pkt, tlv_type = None):
|
|||
val = str(val.split(NULL_BYTE, 1)[0])
|
||||
elif (tlv[1] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT:
|
||||
val = struct.unpack('>I', val)[0]
|
||||
elif (tlv[1] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD:
|
||||
val = struct.unpack('>Q', val)[0]
|
||||
elif (tlv[1] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL:
|
||||
val = bool(struct.unpack('b', val)[0])
|
||||
elif (tlv[1] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW:
|
||||
|
@ -175,6 +178,8 @@ def tlv_pack(*args):
|
|||
data = ""
|
||||
if (tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT:
|
||||
data = struct.pack('>III', 12, tlv['type'], tlv['value'])
|
||||
elif (tlv['type'] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD:
|
||||
data = struct.pack('>IIQ', 16, tlv['type'], tlv['value'])
|
||||
elif (tlv['type'] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL:
|
||||
data = struct.pack('>II', 9, tlv['type']) + bytes(chr(int(bool(tlv['value']))), 'UTF-8')
|
||||
else:
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -13,7 +13,7 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerSimple < BaseParser
|
||||
|
@ -24,23 +24,20 @@ class CrawlerSimple < BaseParser
|
|||
return
|
||||
end
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search('a').each do |link|
|
||||
|
||||
hr = link.attributes['href']
|
||||
|
||||
if hr and !hr.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET',hr,request['uri'],nil)
|
||||
|
||||
insertnewpath(hreq)
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
# doc = Hpricot(result.body.to_s)
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.css('a').each do |anchor_tag|
|
||||
hr = anchor_tag['href']
|
||||
if hr && !hr.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET', hr, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerForms < BaseParser
|
||||
|
@ -27,49 +27,30 @@ class CrawlerForms < BaseParser
|
|||
hr = ''
|
||||
m = ''
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search('form').each do |f|
|
||||
hr = f.attributes['action']
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.css('form').each do |f|
|
||||
hr = f['action']
|
||||
|
||||
fname = f.attributes['name']
|
||||
if fname.empty?
|
||||
fname = "NONE"
|
||||
end
|
||||
fname = f['name']
|
||||
fname = "NONE" if fname.empty?
|
||||
|
||||
m = "GET"
|
||||
if !f.attributes['method'].empty?
|
||||
m = f.attributes['method'].upcase
|
||||
end
|
||||
m = f['method'].empty? ? 'GET' : f['method'].upcase
|
||||
|
||||
#puts "Parsing form name: #{fname} (#{m})"
|
||||
|
||||
htmlform = Hpricot(f.inner_html)
|
||||
htmlform = Nokogiri::HTML(f.inner_html)
|
||||
|
||||
arrdata = []
|
||||
|
||||
htmlform.search('input').each do |p|
|
||||
#puts p.attributes['name']
|
||||
#puts p.attributes['type']
|
||||
#puts p.attributes['value']
|
||||
|
||||
#raw_request has uri_encoding disabled as it encodes '='.
|
||||
arrdata << (p.attributes['name'] + "=" + Rex::Text.uri_encode(p.attributes['value']))
|
||||
htmlform.css('input').each do |p|
|
||||
arrdata << "#{p['name']}=#{Rex::Text.uri_encode(p['value'])}"
|
||||
end
|
||||
|
||||
data = arrdata.join("&").to_s
|
||||
|
||||
|
||||
begin
|
||||
hreq = urltohash(m,hr,request['uri'],data)
|
||||
|
||||
hreq = urltohash(m, hr, request['uri'], data)
|
||||
hreq['ctype'] = 'application/x-www-form-urlencoded'
|
||||
|
||||
insertnewpath(hreq)
|
||||
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,33 +9,29 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerFrames < BaseParser
|
||||
|
||||
def parse(request,result)
|
||||
|
||||
if !result['Content-Type'].include? "text/html"
|
||||
return
|
||||
end
|
||||
return unless result['Content-Type'].include?('text/html')
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search('iframe').each do |ifra|
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.css('iframe').each do |ifra|
|
||||
ir = ifra['src']
|
||||
|
||||
ir = ifra.attributes['src']
|
||||
|
||||
if ir and !ir.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET',ir,request['uri'],nil)
|
||||
|
||||
insertnewpath(hreq)
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Error"
|
||||
if ir && !ir.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET', ir, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
rescue URI::InvalidURIError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -10,33 +10,26 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerImage < BaseParser
|
||||
|
||||
def parse(request,result)
|
||||
|
||||
if !result['Content-Type'].include? "text/html"
|
||||
return
|
||||
end
|
||||
return unless result['Content-Type'].include?('text/html')
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search('img').each do |i|
|
||||
|
||||
im = i.attributes['src']
|
||||
|
||||
if im and !im.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET',im,request['uri'],nil)
|
||||
|
||||
insertnewpath(hreq)
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{i[0]}"
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.css('img').each do |i|
|
||||
im = i['src']
|
||||
if im && !im.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET', im, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
rescue URI::InvalidURIError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,33 +10,25 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerLink < BaseParser
|
||||
|
||||
def parse(request,result)
|
||||
return unless result['Content-Type'].include?('text/html')
|
||||
|
||||
if !result['Content-Type'].include? "text/html"
|
||||
return
|
||||
end
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search('link').each do |link|
|
||||
|
||||
hr = link.attributes['href']
|
||||
|
||||
if hr and !hr.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET',hr,request['uri'],nil)
|
||||
|
||||
insertnewpath(hreq)
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.css('link').each do |link|
|
||||
hr = link['href']
|
||||
if hr && !hr.match(/^(\#|javascript\:)/)
|
||||
begin
|
||||
hreq = urltohash('GET', hr, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
rescue URI::InvalidURIError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,36 +13,25 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerObjects < BaseParser
|
||||
|
||||
def parse(request,result)
|
||||
|
||||
if !result['Content-Type'].include? "text/html"
|
||||
return
|
||||
end
|
||||
|
||||
return unless result['Content-Type'].include?('text/html') # TOOD: use MIXIN
|
||||
hr = ''
|
||||
m = ''
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search("//object/embed").each do |obj|
|
||||
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.xpath("//object/embed").each do |obj|
|
||||
s = obj['src']
|
||||
|
||||
begin
|
||||
hreq = urltohash('GET',s,request['uri'],nil)
|
||||
|
||||
hreq = urltohash('GET', s, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -13,36 +13,27 @@
|
|||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'hpricot'
|
||||
require 'nokogiri'
|
||||
require 'uri'
|
||||
|
||||
class CrawlerScripts < BaseParser
|
||||
|
||||
def parse(request,result)
|
||||
|
||||
if !result['Content-Type'].include? "text/html"
|
||||
return
|
||||
end
|
||||
return unless result['Content-Type'].include? "text/html"
|
||||
|
||||
hr = ''
|
||||
m = ''
|
||||
|
||||
doc = Hpricot(result.body.to_s)
|
||||
doc.search("//script").each do |obj|
|
||||
|
||||
doc = Nokogiri::HTML(result.body.to_s)
|
||||
doc.xpath("//script").each do |obj|
|
||||
s = obj['src']
|
||||
|
||||
begin
|
||||
hreq = urltohash('GET',s,request['uri'],nil)
|
||||
|
||||
hreq = urltohash('GET', s, request['uri'], nil)
|
||||
insertnewpath(hreq)
|
||||
|
||||
|
||||
rescue URI::InvalidURIError
|
||||
#puts "Parse error"
|
||||
#puts "Error: #{link[0]}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
$magic = 'TzGq';
|
||||
$tempdir = sys_get_temp_dir() . "/hop" . $magic;
|
||||
if(!is_dir($tempdir)){
|
||||
mkdir($tempdir); //make sure it's there
|
||||
}
|
||||
|
||||
//get url
|
||||
$url = $_SERVER["QUERY_STRING"];
|
||||
//like /path/hop.php?/uRIcksm_lOnGidENTifIEr
|
||||
|
||||
//Looks for a file with a name or contents prefix, if found, send it and deletes it
|
||||
function findSendDelete($tempdir, $prefix, $one=true){
|
||||
if($dh = opendir($tempdir)){
|
||||
while(($file = readdir($dh)) !== false){
|
||||
if(strpos($file, $prefix) !== 0){
|
||||
continue;
|
||||
}
|
||||
readfile($tempdir."/".$file);
|
||||
unlink($tempdir."/".$file);
|
||||
if($one){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//handle control
|
||||
if($url === "/control"){
|
||||
if($_SERVER['REQUEST_METHOD'] === 'POST'){
|
||||
//handle data for payload - save in a "down" file or the "init" file
|
||||
$postdata = file_get_contents("php://input");
|
||||
if(array_key_exists('HTTP_X_INIT', $_SERVER)){
|
||||
$f = fopen($tempdir."/init", "w"); //only one init file
|
||||
}else{
|
||||
$prefix = "down_" . bin2hex($_SERVER['HTTP_X_URLFRAG']);
|
||||
$f = fopen(tempnam($tempdir,$prefix), "w");
|
||||
}
|
||||
fwrite($f, $postdata);
|
||||
fclose($f);
|
||||
}else{
|
||||
findSendDelete($tempdir, "up_", false);
|
||||
}
|
||||
}else if($_SERVER['REQUEST_METHOD'] === 'POST'){
|
||||
//get data
|
||||
$postdata = file_get_contents("php://input");
|
||||
//See if we should send anything down
|
||||
if($postdata === 'RECV'){
|
||||
findSendDelete($tempdir, "down_" . bin2hex($url));
|
||||
$fname = $tempdir . "/up_recv_" . bin2hex($url); //Only keep one RECV poll
|
||||
}else{
|
||||
$fname = tempnam($tempdir, "up_"); //actual data gets its own filename
|
||||
}
|
||||
//find free and write new file
|
||||
$f = fopen($fname, "w");
|
||||
fwrite($f, $magic);
|
||||
//Little-endian pack length and data
|
||||
$urlen = strlen($url);
|
||||
fwrite($f, pack('V', $urlen));
|
||||
fwrite($f, $url);
|
||||
$postdatalen = strlen($postdata);
|
||||
fwrite($f, pack('V', $postdatalen));
|
||||
fwrite($f, $postdata);
|
||||
fclose($f);
|
||||
//Initial query will be a GET and have a 12345 in it
|
||||
}else if(strpos($url, "12345") !== FALSE){
|
||||
readfile($tempdir."/init");
|
||||
}
|
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
|
||||
|
|
|
@ -37,7 +37,7 @@ class BitStruct
|
|||
old_writer = "#{attr_chars}="
|
||||
|
||||
define_method "#{attr}=" do |val|
|
||||
data = val.split(sep).map{|s|s.to_i(base)}.pack("c*")
|
||||
data = val.split(sep).map{|s|s.to_i(base)}.pack("C*")
|
||||
send(old_writer, data)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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,113 @@
|
|||
#
|
||||
# Gems
|
||||
#
|
||||
|
||||
require 'active_support/core_ext/module/introspection'
|
||||
|
||||
#
|
||||
# Project
|
||||
#
|
||||
|
||||
require 'metasploit/framework/command'
|
||||
require 'metasploit/framework/parsed_options'
|
||||
require 'metasploit/framework/require'
|
||||
|
||||
# Based on pattern used for lib/rails/commands in the railties gem.
|
||||
class Metasploit::Framework::Command::Base
|
||||
#
|
||||
# Attributes
|
||||
#
|
||||
|
||||
# @!attribute [r] application
|
||||
# The Rails application for metasploit-framework.
|
||||
#
|
||||
# @return [Metasploit::Framework::Application]
|
||||
attr_reader :application
|
||||
|
||||
# @!attribute [r] parsed_options
|
||||
# The parsed options from the command line.
|
||||
#
|
||||
# @return (see parsed_options)
|
||||
attr_reader :parsed_options
|
||||
|
||||
#
|
||||
# Class Methods
|
||||
#
|
||||
|
||||
# @note {require_environment!} should be called to load
|
||||
# `config/application.rb` to so that the RAILS_ENV can be set from the
|
||||
# command line options in `ARGV` prior to `Rails.env` being set.
|
||||
# @note After returning, `Rails.application` will be defined and configured.
|
||||
#
|
||||
# Parses `ARGV` for command line arguments to configure the
|
||||
# `Rails.application`.
|
||||
#
|
||||
# @return (see parsed_options)
|
||||
def self.require_environment!
|
||||
parsed_options = self.parsed_options
|
||||
# RAILS_ENV must be set before requiring 'config/application.rb'
|
||||
parsed_options.environment!
|
||||
ARGV.replace(parsed_options.positional)
|
||||
|
||||
# allow other Rails::Applications to use this command
|
||||
if !defined?(Rails) || Rails.application.nil?
|
||||
# @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/commands.rb#L39-L40
|
||||
require Pathname.new(__FILE__).parent.parent.parent.parent.parent.join('config', 'application')
|
||||
end
|
||||
|
||||
# have to configure before requiring environment because
|
||||
# config/environment.rb calls initialize! and the initializers will use
|
||||
# the configuration from the parsed options.
|
||||
parsed_options.configure(Rails.application)
|
||||
|
||||
# support disabling the database
|
||||
unless parsed_options.options.database.disable
|
||||
Metasploit::Framework::Require.optionally_active_record_railtie
|
||||
end
|
||||
|
||||
Rails.application.require_environment!
|
||||
|
||||
parsed_options
|
||||
end
|
||||
|
||||
def self.parsed_options
|
||||
parsed_options_class.new
|
||||
end
|
||||
|
||||
def self.parsed_options_class
|
||||
@parsed_options_class ||= parsed_options_class_name.constantize
|
||||
end
|
||||
|
||||
def self.parsed_options_class_name
|
||||
@parsed_options_class_name ||= "#{parent.parent}::ParsedOptions::#{name.demodulize}"
|
||||
end
|
||||
|
||||
def self.start
|
||||
parsed_options = require_environment!
|
||||
new(application: Rails.application, parsed_options: parsed_options).start
|
||||
end
|
||||
|
||||
#
|
||||
# Instance Methods
|
||||
#
|
||||
|
||||
# @param attributes [Hash{Symbol => ActiveSupport::OrderedOptions,Rails::Application}]
|
||||
# @option attributes [Rails::Application] :application
|
||||
# @option attributes [ActiveSupport::OrderedOptions] :parsed_options
|
||||
# @raise [KeyError] if :application is not given
|
||||
# @raise [KeyError] if :parsed_options is not given
|
||||
def initialize(attributes={})
|
||||
@application = attributes.fetch(:application)
|
||||
@parsed_options = attributes.fetch(:parsed_options)
|
||||
end
|
||||
|
||||
# @abstract Use {#application} to start this command.
|
||||
#
|
||||
# Starts this command.
|
||||
#
|
||||
# @return [void]
|
||||
# @raise [NotImplementedError]
|
||||
def start
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
|
@ -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,105 @@
|
|||
require 'active_model'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
# This class provides an in-memory representation of a conceptual Credential
|
||||
#
|
||||
# It contains the public, private, and realm if any.
|
||||
class Credential
|
||||
include ActiveModel::Validations
|
||||
|
||||
# @!attribute paired
|
||||
# @return [Boolean] Whether BOTH a public and private are required
|
||||
# (defaults to `true`)
|
||||
attr_accessor :paired
|
||||
# @!attribute parent
|
||||
# @return [Object] the parent object that had .to_credential called on it to create this object
|
||||
attr_accessor :parent
|
||||
# @!attribute private
|
||||
# The private credential component (e.g. username)
|
||||
#
|
||||
# @return [String] if {#paired} is `true` or {#private} is `nil`
|
||||
# @return [String, nil] if {#paired} is `false` or {#private} is not `nil`.
|
||||
attr_accessor :private
|
||||
# @!attribute private_type
|
||||
# The type of private credential this object represents, e.g. a
|
||||
# password or an NTLM hash.
|
||||
#
|
||||
# @return [String]
|
||||
attr_accessor :private_type
|
||||
# @!attribute public
|
||||
# The public credential component (e.g. password)
|
||||
#
|
||||
# @return [String] if {#paired} is `true` or {#public} is `nil`
|
||||
# @return [String, nil] if {#paired} is `false` or {#public} is not `nil`.
|
||||
attr_accessor :public
|
||||
# @!attribute realm
|
||||
# @return [String,nil] The realm credential component (e.g domain name)
|
||||
attr_accessor :realm
|
||||
# @!attribute realm
|
||||
# @return [String,nil] The type of {#realm}
|
||||
attr_accessor :realm_key
|
||||
|
||||
validates :paired,
|
||||
inclusion: { in: [true, false] }
|
||||
|
||||
# If we have no public we MUST have a private (e.g. SNMP Community String)
|
||||
validates :private,
|
||||
exclusion: { in: [nil] },
|
||||
if: "public.nil? or paired"
|
||||
|
||||
# These values should be #demodularized from subclasses of
|
||||
# `Metasploit::Credential::Private`
|
||||
validates :private_type,
|
||||
inclusion: { in: [ :password, :ntlm_hash, :ssh_key ] },
|
||||
if: "private_type.present?"
|
||||
|
||||
# If we have no private we MUST have a public
|
||||
validates :public,
|
||||
presence: true,
|
||||
if: "private.nil? or paired"
|
||||
|
||||
# @param attributes [Hash{Symbol => String,nil}]
|
||||
def initialize(attributes={})
|
||||
attributes.each do |attribute, value|
|
||||
public_send("#{attribute}=", value)
|
||||
end
|
||||
|
||||
self.paired = true if self.paired.nil?
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} \"#{self}\" >"
|
||||
end
|
||||
|
||||
def to_s
|
||||
if realm && realm_key == Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
|
||||
"#{self.realm}\\#{self.public}:#{self.private}"
|
||||
else
|
||||
"#{self.public}:#{self.private}#{at_realm}"
|
||||
end
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other.respond_to?(:public) && other.public == self.public &&
|
||||
other.respond_to?(:private) && other.private == self.private &&
|
||||
other.respond_to?(:realm) && other.realm == self.realm
|
||||
end
|
||||
|
||||
def to_credential
|
||||
self.parent = self
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def at_realm
|
||||
if self.realm.present?
|
||||
"@#{self.realm}"
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,148 @@
|
|||
require 'metasploit/framework/credential'
|
||||
|
||||
class Metasploit::Framework::CredentialCollection
|
||||
|
||||
# @!attribute blank_passwords
|
||||
# Whether each username should be tried with a blank password
|
||||
# @return [Boolean]
|
||||
attr_accessor :blank_passwords
|
||||
|
||||
# @!attribute pass_file
|
||||
# Path to a file containing passwords, one per line
|
||||
# @return [String]
|
||||
attr_accessor :pass_file
|
||||
|
||||
# @!attribute password
|
||||
# @return [String]
|
||||
attr_accessor :password
|
||||
|
||||
# @!attribute prepended_creds
|
||||
# List of credentials to be tried before any others
|
||||
#
|
||||
# @see #prepend_cred
|
||||
# @return [Array<Credential>]
|
||||
attr_accessor :prepended_creds
|
||||
|
||||
# @!attribute realm
|
||||
# @return [String]
|
||||
attr_accessor :realm
|
||||
|
||||
# @!attribute user_as_pass
|
||||
# Whether each username should be tried as a password for that user
|
||||
# @return [Boolean]
|
||||
attr_accessor :user_as_pass
|
||||
|
||||
# @!attribute user_file
|
||||
# Path to a file containing usernames, one per line
|
||||
# @return [String]
|
||||
attr_accessor :user_file
|
||||
|
||||
# @!attribute username
|
||||
# @return [String]
|
||||
attr_accessor :username
|
||||
|
||||
# @!attribute userpass_file
|
||||
# Path to a file containing usernames and passwords separated by a space,
|
||||
# one pair per line
|
||||
# @return [String]
|
||||
attr_accessor :userpass_file
|
||||
|
||||
# @option opts [Boolean] :blank_passwords See {#blank_passwords}
|
||||
# @option opts [String] :pass_file See {#pass_file}
|
||||
# @option opts [String] :password See {#password}
|
||||
# @option opts [Array<Credential>] :prepended_creds ([]) See {#prepended_creds}
|
||||
# @option opts [Boolean] :user_as_pass See {#user_as_pass}
|
||||
# @option opts [String] :user_file See {#user_file}
|
||||
# @option opts [String] :username See {#username}
|
||||
# @option opts [String] :userpass_file See {#userpass_file}
|
||||
def initialize(opts = {})
|
||||
opts.each do |attribute, value|
|
||||
public_send("#{attribute}=", value)
|
||||
end
|
||||
self.prepended_creds ||= []
|
||||
end
|
||||
|
||||
# Add {Credential credentials} that will be yielded by {#each}
|
||||
#
|
||||
# @see prepended_creds
|
||||
# @param cred [Credential]
|
||||
# @return [self]
|
||||
def prepend_cred(cred)
|
||||
prepended_creds.unshift cred
|
||||
self
|
||||
end
|
||||
|
||||
# Combines all the provided credential sources into a stream of {Credential}
|
||||
# objects, yielding them one at a time
|
||||
#
|
||||
# @yieldparam credential [Metasploit::Framework::Credential]
|
||||
# @return [void]
|
||||
def each
|
||||
if pass_file.present?
|
||||
pass_fd = File.open(pass_file, 'r:binary')
|
||||
end
|
||||
|
||||
prepended_creds.each { |c| yield c }
|
||||
|
||||
if username.present?
|
||||
if password.present?
|
||||
yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm)
|
||||
end
|
||||
if user_as_pass
|
||||
yield Metasploit::Framework::Credential.new(public: username, private: username, realm: realm)
|
||||
end
|
||||
if blank_passwords
|
||||
yield Metasploit::Framework::Credential.new(public: username, private: "", realm: realm)
|
||||
end
|
||||
if pass_fd
|
||||
pass_fd.each_line do |pass_from_file|
|
||||
pass_from_file.chomp!
|
||||
yield Metasploit::Framework::Credential.new(public: username, private: pass_from_file, realm: realm)
|
||||
end
|
||||
pass_fd.seek(0)
|
||||
end
|
||||
end
|
||||
|
||||
if user_file.present?
|
||||
File.open(user_file, 'r:binary') do |user_fd|
|
||||
user_fd.each_line do |user_from_file|
|
||||
user_from_file.chomp!
|
||||
if password
|
||||
yield Metasploit::Framework::Credential.new(public: user_from_file, private: password, realm: realm)
|
||||
end
|
||||
if user_as_pass
|
||||
yield Metasploit::Framework::Credential.new(public: user_from_file, private: user_from_file, realm: realm)
|
||||
end
|
||||
if blank_passwords
|
||||
yield Metasploit::Framework::Credential.new(public: user_from_file, private: "", realm: realm)
|
||||
end
|
||||
if pass_fd
|
||||
pass_fd.each_line do |pass_from_file|
|
||||
pass_from_file.chomp!
|
||||
yield Metasploit::Framework::Credential.new(public: user_from_file, private: pass_from_file, realm: realm)
|
||||
end
|
||||
pass_fd.seek(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if userpass_file.present?
|
||||
File.open(userpass_file, 'r:binary') do |userpass_fd|
|
||||
userpass_fd.each_line do |line|
|
||||
user, pass = line.split(" ", 2)
|
||||
if pass.blank?
|
||||
pass = ''
|
||||
else
|
||||
pass.chomp!
|
||||
end
|
||||
yield Metasploit::Framework::Credential.new(public: user, private: pass, realm: realm)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ensure
|
||||
pass_fd.close if pass_fd && !pass_fd.closed?
|
||||
end
|
||||
|
||||
end
|
|
@ -8,7 +8,7 @@ module Metasploit
|
|||
end
|
||||
|
||||
def self.configurations_pathname
|
||||
Metasploit::Framework.root.join('config', 'database.yml')
|
||||
Metasploit::Framework::Application.paths['config/database'].first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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,50 @@
|
|||
require 'metasploit/framework/tcp/client'
|
||||
require 'metasploit/framework/afp/client'
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# This is the LoginScanner class for dealing with Apple Filing
|
||||
# Protocol.
|
||||
class AFP
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
include Metasploit::Framework::Tcp::Client
|
||||
include Metasploit::Framework::AFP::Client
|
||||
|
||||
DEFAULT_PORT = 548
|
||||
LIKELY_PORTS = [ DEFAULT_PORT ]
|
||||
LIKELY_SERVICE_NAMES = [ "afp" ]
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
REALM_KEY = nil
|
||||
|
||||
# @!attribute login_timeout
|
||||
# @return [Integer] Number of seconds to wait before giving up
|
||||
attr_accessor :login_timeout
|
||||
|
||||
def attempt_login(credential)
|
||||
begin
|
||||
connect
|
||||
rescue Rex::ConnectionError, EOFError, Timeout::Error
|
||||
status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
else
|
||||
success = login(credential.public, credential.private)
|
||||
status = (success == true) ? Metasploit::Model::Login::Status::SUCCESSFUL : Metasploit::Model::Login::Status::INCORRECT
|
||||
end
|
||||
|
||||
Result.new(credential: credential, status: status)
|
||||
end
|
||||
|
||||
def set_sane_defaults
|
||||
self.connection_timeout ||= 30
|
||||
self.port ||= DEFAULT_PORT
|
||||
self.max_send_size ||= 0
|
||||
self.send_delay ||= 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,67 @@
|
|||
|
||||
require 'metasploit/framework/login_scanner/http'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# Tomcat Manager login scanner
|
||||
class Axis2 < HTTP
|
||||
|
||||
DEFAULT_PORT = 8080
|
||||
# Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP
|
||||
|
||||
CAN_GET_SESSION = true
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
|
||||
# (see Base#attempt_login)
|
||||
def attempt_login(credential)
|
||||
http_client = Rex::Proto::Http::Client.new(
|
||||
host, port, {}, ssl, ssl_version
|
||||
)
|
||||
|
||||
result_opts = {
|
||||
credential: credential
|
||||
}
|
||||
begin
|
||||
http_client.connect
|
||||
body = "userName=#{Rex::Text.uri_encode(credential.public)}&password=#{Rex::Text.uri_encode(credential.private)}&submit=+Login+"
|
||||
request = http_client.request_cgi(
|
||||
'uri' => uri,
|
||||
'method' => "POST",
|
||||
'data' => body,
|
||||
)
|
||||
response = http_client.send_recv(request)
|
||||
|
||||
if response && response.code == 200 && response.body.include?("upload")
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response)
|
||||
else
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: response)
|
||||
end
|
||||
rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
|
||||
end
|
||||
|
||||
Result.new(result_opts)
|
||||
|
||||
end
|
||||
|
||||
# (see Base#set_sane_defaults)
|
||||
def set_sane_defaults
|
||||
self.uri = "/axis2/axis2-admin/login" if self.uri.nil?
|
||||
@method = "POST".freeze
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# The method *must* be "POST", so don't let the user change it
|
||||
# @raise [RuntimeError]
|
||||
def method=(_)
|
||||
raise RuntimeError, "Method must be POST for Axis2"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -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,134 @@
|
|||
require 'metasploit/framework/tcp/client'
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
# This is the LoginScanner class for dealing with DB2 Database servers.
|
||||
# It is responsible for taking a single target, and a list of credentials
|
||||
# and attempting them. It then saves the results.
|
||||
class DB2
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
include Metasploit::Framework::Tcp::Client
|
||||
|
||||
DEFAULT_PORT = 50000
|
||||
DEFAULT_REALM = 'toolsdb'
|
||||
LIKELY_PORTS = [ DEFAULT_PORT ]
|
||||
# @todo XXX
|
||||
LIKELY_SERVICE_NAMES = [ ]
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
REALM_KEY = Metasploit::Model::Realm::Key::DB2_DATABASE
|
||||
|
||||
# @see Base#attempt_login
|
||||
def attempt_login(credential)
|
||||
result_options = {
|
||||
credential: credential
|
||||
}
|
||||
|
||||
begin
|
||||
probe_data = send_probe(credential.realm)
|
||||
|
||||
if probe_data.empty?
|
||||
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
else
|
||||
if authenticate?(credential)
|
||||
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
else
|
||||
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
|
||||
end
|
||||
end
|
||||
rescue ::Rex::ConnectionError, ::Rex::ConnectionTimeout, ::Rex::Proto::DRDA::RespError,::Timeout::Error => e
|
||||
result_options.merge!({
|
||||
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
|
||||
proof: e.message
|
||||
})
|
||||
end
|
||||
|
||||
::Metasploit::Framework::LoginScanner::Result.new(result_options)
|
||||
end
|
||||
|
||||
private
|
||||
# This method takes the credential and actually attempts the authentication
|
||||
# @param credential [Credential] The Credential object to authenticate with.
|
||||
# @return [Boolean] Whether the authentication was successful
|
||||
def authenticate?(credential)
|
||||
# Send the login packet and get a response packet back
|
||||
login_packet = Rex::Proto::DRDA::Utils.client_auth(:dbname => credential.realm,
|
||||
:dbuser => credential.public,
|
||||
:dbpass => credential.private
|
||||
)
|
||||
sock.put login_packet
|
||||
response = sock.get_once
|
||||
if valid_response?(response)
|
||||
if successful_login?(response)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# This method opens a socket to the target DB2 server.
|
||||
# It then sends a client probe on that socket to get information
|
||||
# back on the server.
|
||||
# @param database_name [String] The name of the database to probe
|
||||
# @return [Hash] A hash containing the server information from the probe reply
|
||||
def send_probe(database_name)
|
||||
disconnect if self.sock
|
||||
connect
|
||||
|
||||
probe_packet = Rex::Proto::DRDA::Utils.client_probe(database_name)
|
||||
sock.put probe_packet
|
||||
response = sock.get_once
|
||||
|
||||
response_data = {}
|
||||
if valid_response?(response)
|
||||
packet = Rex::Proto::DRDA::SERVER_PACKET.new.read(response)
|
||||
response_data = Rex::Proto::DRDA::Utils.server_packet_info(packet)
|
||||
end
|
||||
response_data
|
||||
end
|
||||
|
||||
# This method sets the sane defaults for things
|
||||
# like timeouts and TCP evasion options
|
||||
def set_sane_defaults
|
||||
self.connection_timeout ||= 30
|
||||
self.port ||= DEFAULT_PORT
|
||||
self.max_send_size ||= 0
|
||||
self.send_delay ||= 0
|
||||
|
||||
self.ssl = false if self.ssl.nil?
|
||||
end
|
||||
|
||||
# This method takes a response packet and checks to see
|
||||
# if the authentication was actually successful.
|
||||
#
|
||||
# @param response [String] The unprocessed response packet
|
||||
# @return [Boolean] Whether the authentication was successful
|
||||
def successful_login?(response)
|
||||
packet = Rex::Proto::DRDA::SERVER_PACKET.new.read(response)
|
||||
packet_info = Rex::Proto::DRDA::Utils.server_packet_info(packet)
|
||||
if packet_info[:db_login_success]
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# This method provides a simple test on whether the response
|
||||
# packet was valid.
|
||||
#
|
||||
# @param response [String] The response to examine from the socket
|
||||
# @return [Boolean] Whether the response is valid
|
||||
def valid_response?(response)
|
||||
response && response.length > 0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,76 @@
|
|||
require 'metasploit/framework/ftp/client'
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# This is the LoginScanner class for dealing with FTP.
|
||||
# It is responsible for taking a single target, and a list of credentials
|
||||
# and attempting them. It then saves the results.
|
||||
class FTP
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
include Metasploit::Framework::Ftp::Client
|
||||
|
||||
DEFAULT_PORT = 21
|
||||
LIKELY_PORTS = [ DEFAULT_PORT, 2121 ]
|
||||
LIKELY_SERVICE_NAMES = [ 'ftp' ]
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
REALM_KEY = nil
|
||||
|
||||
# @!attribute ftp_timeout
|
||||
# @return [Fixnum] The timeout in seconds to wait for a response to an FTP command
|
||||
attr_accessor :ftp_timeout
|
||||
|
||||
validates :ftp_timeout,
|
||||
presence: true,
|
||||
numericality: {
|
||||
only_integer: true,
|
||||
greater_than_or_equal_to: 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
# (see Base#attempt_login)
|
||||
def attempt_login(credential)
|
||||
result_options = {
|
||||
credential: credential
|
||||
}
|
||||
|
||||
begin
|
||||
success = connect_login(credential.public, credential.private)
|
||||
rescue ::EOFError, Rex::AddressInUse, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error
|
||||
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
success = false
|
||||
end
|
||||
|
||||
|
||||
if success
|
||||
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
elsif !(result_options.has_key? :status)
|
||||
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
|
||||
end
|
||||
|
||||
::Metasploit::Framework::LoginScanner::Result.new(result_options)
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This method sets the sane defaults for things
|
||||
# like timeouts and TCP evasion options
|
||||
def set_sane_defaults
|
||||
self.connection_timeout ||= 30
|
||||
self.port ||= DEFAULT_PORT
|
||||
self.max_send_size ||= 0
|
||||
self.send_delay ||= 0
|
||||
self.ftp_timeout ||= 16
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,123 @@
|
|||
require 'rex/proto/http'
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
#
|
||||
# HTTP-specific login scanner.
|
||||
#
|
||||
class HTTP
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
|
||||
DEFAULT_REALM = nil
|
||||
DEFAULT_PORT = 80
|
||||
DEFAULT_SSL_PORT = 443
|
||||
LIKELY_PORTS = [ 80, 443, 8000, 8080 ]
|
||||
LIKELY_SERVICE_NAMES = [ 'http', 'https' ]
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
|
||||
|
||||
# @!attribute uri
|
||||
# @return [String] The path and query string on the server to
|
||||
# authenticate to.
|
||||
attr_accessor :uri
|
||||
|
||||
# @!attribute uri
|
||||
# @return [String] HTTP method, e.g. "GET", "POST"
|
||||
attr_accessor :method
|
||||
|
||||
validates :uri, presence: true, length: { minimum: 1 }
|
||||
|
||||
validates :method,
|
||||
presence: true,
|
||||
length: { minimum: 1 }
|
||||
|
||||
# Attempt a single login with a single credential against the target.
|
||||
#
|
||||
# @param credential [Credential] The credential object to attempt to
|
||||
# login with.
|
||||
# @return [Result] A Result object indicating success or failure
|
||||
def attempt_login(credential)
|
||||
ssl = false if ssl.nil?
|
||||
|
||||
result_opts = {
|
||||
credential: credential,
|
||||
status: Metasploit::Model::Login::Status::INCORRECT,
|
||||
proof: nil
|
||||
}
|
||||
|
||||
http_client = Rex::Proto::Http::Client.new(
|
||||
host, port, {}, ssl, ssl_version,
|
||||
nil, credential.public, credential.private
|
||||
)
|
||||
if credential.realm
|
||||
http_client.set_config('domain' => credential.realm)
|
||||
end
|
||||
|
||||
begin
|
||||
http_client.connect
|
||||
request = http_client.request_cgi(
|
||||
'uri' => uri,
|
||||
'method' => method
|
||||
)
|
||||
|
||||
# First try to connect without logging in to make sure this
|
||||
# resource requires authentication. We use #_send_recv for
|
||||
# that instead of #send_recv.
|
||||
response = http_client._send_recv(request)
|
||||
if response && response.code == 401 && response.headers['WWW-Authenticate']
|
||||
# Now send the creds
|
||||
response = http_client.send_auth(
|
||||
response, request.opts, connection_timeout, true
|
||||
)
|
||||
if response && response.code == 200
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers)
|
||||
end
|
||||
else
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::NO_AUTH_REQUIRED)
|
||||
end
|
||||
rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
|
||||
ensure
|
||||
http_client.close
|
||||
end
|
||||
|
||||
Result.new(result_opts)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This method sets the sane defaults for things
|
||||
# like timeouts and TCP evasion options
|
||||
def set_sane_defaults
|
||||
self.connection_timeout ||= 20
|
||||
self.max_send_size = 0 if self.max_send_size.nil?
|
||||
self.send_delay = 0 if self.send_delay.nil?
|
||||
self.uri = '/' if self.uri.blank?
|
||||
self.method = 'GET' if self.method.blank?
|
||||
|
||||
# Note that this doesn't cover the case where ssl is unset and
|
||||
# port is something other than a default. In that situtation,
|
||||
# we don't know what the user has in mind so we have to trust
|
||||
# that they're going to do something sane.
|
||||
if !(self.ssl) && self.port.nil?
|
||||
self.port = self.class::DEFAULT_PORT
|
||||
self.ssl = false
|
||||
elsif self.ssl && self.port.nil?
|
||||
self.port = self.class::DEFAULT_SSL_PORT
|
||||
elsif self.ssl.nil? && self.port == self.class::DEFAULT_PORT
|
||||
self.ssl = false
|
||||
elsif self.ssl.nil? && self.port == self.class::DEFAULT_SSL_PORT
|
||||
self.ssl = true
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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,74 @@
|
|||
require 'metasploit/framework/mssql/client'
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
require 'metasploit/framework/login_scanner/ntlm'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# This is the LoginScanner class for dealing with Microsoft SQL Servers.
|
||||
# It is responsible for taking a single target, and a list of credentials
|
||||
# and attempting them. It then saves the results
|
||||
class MSSQL
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
include Metasploit::Framework::LoginScanner::NTLM
|
||||
include Metasploit::Framework::MSSQL::Client
|
||||
|
||||
DEFAULT_PORT = 1433
|
||||
DEFAULT_REALM = 'WORKSTATION'
|
||||
# Lifted from lib/msf/core/exploit/mssql.rb
|
||||
LIKELY_PORTS = [ 1433, 1434, 1435, 14330, 2533, 9152, 2638 ]
|
||||
# Lifted from lib/msf/core/exploit/mssql.rb
|
||||
LIKELY_SERVICE_NAMES = [ 'ms-sql-s', 'ms-sql2000', 'sybase' ]
|
||||
PRIVATE_TYPES = [ :password, :ntlm_hash ]
|
||||
REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
|
||||
|
||||
# @!attribute windows_authentication
|
||||
# @return [Boolean] Whether to use Windows Authentication instead of SQL Server Auth.
|
||||
attr_accessor :windows_authentication
|
||||
|
||||
validates :windows_authentication,
|
||||
inclusion: { in: [true, false] }
|
||||
|
||||
def attempt_login(credential)
|
||||
result_options = {
|
||||
credential: credential
|
||||
}
|
||||
|
||||
begin
|
||||
if mssql_login(credential.public, credential.private, '', credential.realm)
|
||||
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
else
|
||||
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
|
||||
end
|
||||
rescue ::Rex::ConnectionError
|
||||
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
end
|
||||
|
||||
::Metasploit::Framework::LoginScanner::Result.new(result_options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_sane_defaults
|
||||
self.connection_timeout ||= 30
|
||||
self.port ||= DEFAULT_PORT
|
||||
self.max_send_size ||= 0
|
||||
self.send_delay ||= 0
|
||||
|
||||
# Don't use ||= with booleans
|
||||
self.send_lm = true if self.send_lm.nil?
|
||||
self.send_ntlm = true if self.send_ntlm.nil?
|
||||
self.send_spn = true if self.send_spn.nil?
|
||||
self.use_lmkey = false if self.use_lmkey.nil?
|
||||
self.use_ntlm2_session = true if self.use_ntlm2_session.nil?
|
||||
self.use_ntlmv2 = true if self.use_ntlmv2.nil?
|
||||
self.windows_authentication = false if self.windows_authentication.nil?
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,91 @@
|
|||
require 'metasploit/framework/tcp/client'
|
||||
require 'rbmysql'
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# This is the LoginScanner class for dealing with MySQL Database servers.
|
||||
# It is responsible for taking a single target, and a list of credentials
|
||||
# and attempting them. It then saves the results.
|
||||
class MySQL
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
include Metasploit::Framework::Tcp::Client
|
||||
|
||||
DEFAULT_PORT = 3306
|
||||
LIKELY_PORTS = [ 3306 ]
|
||||
LIKELY_SERVICE_NAMES = [ 'mysql' ]
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
REALM_KEY = nil
|
||||
|
||||
def attempt_login(credential)
|
||||
result_options = {
|
||||
credential: credential
|
||||
}
|
||||
|
||||
# manage our behind the scenes socket. Close any existing one and open a new one
|
||||
disconnect if self.sock
|
||||
connect
|
||||
|
||||
begin
|
||||
::RbMysql.connect({
|
||||
:host => host,
|
||||
:port => port,
|
||||
:read_timeout => 300,
|
||||
:write_timeout => 300,
|
||||
:socket => sock,
|
||||
:user => credential.public,
|
||||
:password => credential.private,
|
||||
:db => ''
|
||||
})
|
||||
rescue Errno::ECONNREFUSED
|
||||
result_options.merge!({
|
||||
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
|
||||
proof: "Connection refused"
|
||||
})
|
||||
rescue RbMysql::ClientError
|
||||
result_options.merge!({
|
||||
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
|
||||
proof: "Connection timeout"
|
||||
})
|
||||
rescue Errno::ETIMEDOUT
|
||||
result_options.merge!({
|
||||
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
|
||||
proof: "Operation Timed out"
|
||||
})
|
||||
rescue RbMysql::HostNotPrivileged
|
||||
result_options.merge!({
|
||||
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
|
||||
proof: "Unable to login from this host due to policy"
|
||||
})
|
||||
rescue RbMysql::AccessDeniedError
|
||||
result_options.merge!({
|
||||
status: Metasploit::Model::Login::Status::INCORRECT,
|
||||
proof: "Access Denied"
|
||||
})
|
||||
end
|
||||
|
||||
unless result_options[:status]
|
||||
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
end
|
||||
|
||||
::Metasploit::Framework::LoginScanner::Result.new(result_options)
|
||||
end
|
||||
|
||||
# This method sets the sane defaults for things
|
||||
# like timeouts and TCP evasion options
|
||||
def set_sane_defaults
|
||||
self.connection_timeout ||= 30
|
||||
self.port ||= DEFAULT_PORT
|
||||
self.max_send_size ||= 0
|
||||
self.send_delay ||= 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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,87 @@
|
|||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
require 'metasploit/framework/tcp/client'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# This is the LoginScanner class for dealing with POP3.
|
||||
# It is responsible for taking a single target, and a list of credentials
|
||||
# and attempting them. It then saves the results.
|
||||
class POP3
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
include Metasploit::Framework::Tcp::Client
|
||||
|
||||
DEFAULT_PORT = 110
|
||||
LIKELY_PORTS = [ 110, 995 ]
|
||||
LIKELY_SERVICE_NAMES = [ 'pop3', 'pop3s' ]
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
REALM_KEY = nil
|
||||
|
||||
# This method attempts a single login with a single credential against the target
|
||||
# @param credential [Credential] The credential object to attempt to login with
|
||||
# @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object
|
||||
def attempt_login(credential)
|
||||
result_options = {
|
||||
credential: credential,
|
||||
status: Metasploit::Model::Login::Status::INCORRECT
|
||||
}
|
||||
|
||||
disconnect if self.sock
|
||||
|
||||
begin
|
||||
connect
|
||||
select([sock],nil,nil,0.4)
|
||||
|
||||
# Check to see if we recieved an OK?
|
||||
result_options[:proof] = sock.get_once
|
||||
if result_options[:proof] && result_options[:proof][/^\+OK.*/]
|
||||
# If we received an OK we should send the USER
|
||||
sock.put("USER #{credential.public}\r\n")
|
||||
result_options[:proof] = sock.get_once
|
||||
|
||||
if result_options[:proof] && result_options[:proof][/^\+OK.*/]
|
||||
# If we got an OK after the username we can send the PASS
|
||||
sock.put("PASS #{credential.private}\r\n")
|
||||
# Dovecot has a failed-auth penalty system that maxes at
|
||||
# sleeping for 15 seconds before sending responses to the
|
||||
# PASS command, so bump the timeout to 16.
|
||||
result_options[:proof] = sock.get_once(-1, 16)
|
||||
|
||||
if result_options[:proof] && result_options[:proof][/^\+OK.*/]
|
||||
# if the pass gives an OK, were good to go
|
||||
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e
|
||||
result_options.merge!(
|
||||
proof: e.message,
|
||||
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
)
|
||||
end
|
||||
|
||||
disconnect if self.sock
|
||||
|
||||
Result.new(result_options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# (see Base#set_sane_defaults)
|
||||
def set_sane_defaults
|
||||
self.connection_timeout ||= 30
|
||||
self.port ||= DEFAULT_PORT
|
||||
self.max_send_size ||= 0
|
||||
self.send_delay ||= 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'postgres_msf'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# This is the LoginScanner class for dealing with PostgreSQL database servers.
|
||||
# It is responsible for taking a single target, and a list of credentials
|
||||
# and attempting them. It then saves the results.
|
||||
class Postgres
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
|
||||
DEFAULT_PORT = 5432
|
||||
DEFAULT_REALM = 'template1'
|
||||
LIKELY_PORTS = [ DEFAULT_PORT ]
|
||||
LIKELY_SERVICE_NAMES = [ 'postgres' ]
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
REALM_KEY = Metasploit::Model::Realm::Key::POSTGRESQL_DATABASE
|
||||
|
||||
# This method attempts a single login with a single credential against the target
|
||||
# @param credential [Credential] The credential object to attmpt to login with
|
||||
# @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object
|
||||
def attempt_login(credential)
|
||||
result_options = {
|
||||
credential: credential
|
||||
}
|
||||
|
||||
db_name = credential.realm || 'template1'
|
||||
|
||||
if ::Rex::Socket.is_ipv6?(host)
|
||||
uri = "tcp://[#{host}]:#{port}"
|
||||
else
|
||||
uri = "tcp://#{host}:#{port}"
|
||||
end
|
||||
|
||||
pg_conn = nil
|
||||
|
||||
begin
|
||||
pg_conn = Msf::Db::PostgresPR::Connection.new(db_name,credential.public,credential.private,uri)
|
||||
rescue RuntimeError => e
|
||||
case e.to_s.split("\t")[1]
|
||||
when "C3D000"
|
||||
result_options.merge!({
|
||||
status: Metasploit::Model::Login::Status::INCORRECT,
|
||||
proof: "C3D000, Creds were good but database was bad"
|
||||
})
|
||||
when "C28000", "C28P01"
|
||||
result_options.merge!({
|
||||
status: Metasploit::Model::Login::Status::INCORRECT,
|
||||
proof: "Invalid username or password"
|
||||
})
|
||||
else
|
||||
result_options.merge!({
|
||||
status: Metasploit::Model::Login::Status::INCORRECT,
|
||||
proof: e.message
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if pg_conn
|
||||
pg_conn.close
|
||||
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
else
|
||||
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
|
||||
end
|
||||
|
||||
::Metasploit::Framework::LoginScanner::Result.new(result_options)
|
||||
end
|
||||
end
|
||||
|
||||
def set_sane_defaults
|
||||
self.connection_timeout ||= 30
|
||||
self.port ||= DEFAULT_PORT
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,54 @@
|
|||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# The Result class provides a standard structure in which
|
||||
# LoginScanners can return the result of a login attempt
|
||||
|
||||
class Result
|
||||
include ActiveModel::Validations
|
||||
|
||||
# @!attribute [r] access_level
|
||||
# @return [String] the access level gained
|
||||
attr_reader :access_level
|
||||
# @!attribute [r] credential
|
||||
# @return [Credential] the Credential object the result is for
|
||||
attr_reader :credential
|
||||
# @!attribute [r] proof
|
||||
# @return [String,nil] the proof that the lgoin was successful
|
||||
attr_reader :proof
|
||||
# @!attribute [r] status
|
||||
# @return [String] the status of the attempt. Should be a member of `Metasploit::Model::Login::Status::ALL`
|
||||
attr_reader :status
|
||||
|
||||
validates :status,
|
||||
inclusion: {
|
||||
in: Metasploit::Model::Login::Status::ALL
|
||||
}
|
||||
|
||||
# @param [Hash] opts The options hash for the initializer
|
||||
# @option opts [String] :private The private credential component
|
||||
# @option opts [String] :proof The proof that the login was successful
|
||||
# @option opts [String] :public The public credential component
|
||||
# @option opts [String] :realm The realm credential component
|
||||
# @option opts [String] :status The status code returned
|
||||
def initialize(opts= {})
|
||||
@access_level = opts.fetch(:access_level, nil)
|
||||
@credential = opts.fetch(:credential)
|
||||
@proof = opts.fetch(:proof, nil)
|
||||
@status = opts.fetch(:status)
|
||||
end
|
||||
|
||||
def success?
|
||||
status == Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{credential.public}:#{credential.private}@#{credential.realm} #{status} >"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,64 @@
|
|||
require 'metasploit/framework/login_scanner'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# This module provides the common mixin behaviour for
|
||||
# LoginScanner objects that rely on Rex Sockets for their
|
||||
# underlying communication.
|
||||
module RexSocket
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
|
||||
# @!attribute max_send_size
|
||||
# @return [Fixnum] The max size of the data to encapsulate in a single packet
|
||||
attr_accessor :max_send_size
|
||||
# @!attribute send_delay
|
||||
# @return [Fixnum] The delay between sending packets
|
||||
attr_accessor :send_delay
|
||||
# @!attribute ssl
|
||||
# @return [Boolean] Whether the socket should use ssl
|
||||
attr_accessor :ssl
|
||||
# @!attribute ssl_version
|
||||
# @return [String] The version of SSL to implement
|
||||
attr_accessor :ssl_version
|
||||
|
||||
validates :max_send_size,
|
||||
presence: true,
|
||||
numericality: {
|
||||
only_integer: true,
|
||||
greater_than_or_equal_to: 0
|
||||
}
|
||||
|
||||
validates :send_delay,
|
||||
presence: true,
|
||||
numericality: {
|
||||
only_integer: true,
|
||||
greater_than_or_equal_to: 0
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
|
||||
def chost
|
||||
'0.0.0.0'
|
||||
end
|
||||
|
||||
def cport
|
||||
0
|
||||
end
|
||||
|
||||
def rhost
|
||||
host
|
||||
end
|
||||
|
||||
def rport
|
||||
port
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue