Merge branch 'feature/MSP-9689/jtr_cracker' into staging/electro-release

bug/bundler_fix
James Lee 2014-06-19 10:14:57 -05:00
commit b606448976
No known key found for this signature in database
GPG Key ID: 2D6094C7CEA0A321
16 changed files with 5956 additions and 405 deletions

View File

@ -19,6 +19,8 @@ group :development do
gem 'redcarpet'
# generating documentation
gem 'yard'
# for development and testing purposes
gem 'pry'
end
group :development, :test do

View File

@ -54,6 +54,7 @@ GEM
arel (3.0.3)
bcrypt (3.1.7)
builder (3.0.4)
coderay (1.1.0)
diff-lcs (1.2.5)
erubis (2.7.0)
factory_girl (4.4.0)
@ -75,6 +76,7 @@ GEM
activesupport
metasploit-model (>= 0.24.1.pre.semantic.pre.versioning.pre.2.pre.0, < 0.25)
pg
method_source (0.8.2)
mini_portile (0.6.0)
msgpack (0.5.8)
multi_json (1.0.4)
@ -84,6 +86,10 @@ GEM
packetfu (1.1.9)
pcaprub (0.11.3)
pg (0.17.1)
pry (0.9.12.6)
coderay (~> 1.0)
method_source (~> 0.8)
slop (~> 3.4)
rack (1.4.5)
rack-cache (1.2)
rack (>= 0.4)
@ -128,6 +134,7 @@ GEM
multi_json (~> 1.0.3)
simplecov-html (~> 0.5.3)
simplecov-html (0.5.3)
slop (3.5.0)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
@ -154,6 +161,7 @@ DEPENDENCIES
network_interface (~> 0.0.1)
pcaprub
pg (>= 0.11)
pry
rake (>= 10.0.0)
redcarpet
rspec (>= 2.12)

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,261 @@
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', '--dupe-suppression' ]
if config.present?
cmd << ( "--config=" + config )
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.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}"
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 bianry 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")
runpath = 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 ||= "run.win32.sse2/john.exe"
when /mmx/
run_path ||= "run.win32.mmx/john.exe"
else
run_path ||= "run.win32.any/john.exe"
end
when /x86_64-linux/
fname = "#{cpuinfo_base}/cpuinfo.ia64.bin"
if File.exists? fname
::FileUtils.chmod(0755, fname) rescue nil
data = %x{"#{fname}"} rescue nil
end
case data
when /mmx/
run_path ||= "run.linux.x64.mmx/john"
else
run_path ||= "run.linux.x86.any/john"
end
when /i[\d]86-linux/
fname = "#{cpuinfo_base}/cpuinfo.ia32.bin"
if File.exists? fname
::FileUtils.chmod(0755, fname) rescue nil
data = %x{"#{fname}"} rescue nil
end
case data
when /sse2/
run_path ||= "run.linux.x86.sse2/john"
when /mmx/
run_path ||= "run.linux.x86.mmx/john"
else
run_path ||= "run.linux.x86.any/john"
end
end
end
runpath
end
end
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
password
root
toor

View File

@ -0,0 +1,3 @@
changeme
summer123
admin

View File

@ -0,0 +1,249 @@
require 'spec_helper'
require 'metasploit/framework/jtr/cracker'
describe Metasploit::Framework::JtR::Cracker do
subject(:cracker) { described_class.new }
let(:john_path) { '/path/to/john' }
let(:other_john_path) { '/path/to/other/john' }
let(:session_id) { 'Session1' }
let(:config) { '/path/to/config.conf' }
let(:pot) { '/path/to/john.pot' }
let(:other_pot) { '/path/to/other/pot' }
let(:wordlist) { '/path/to/wordlist' }
let(:hash_path) { '/path/to/hashes' }
let(:nt_format) { 'nt' }
let(:incremental) { 'Digits5' }
let(:rules) { 'Rule34'}
let(:max_runtime) { 5000 }
describe '#binary_path' do
context 'when the user supplied a john_path' do
before(:each) do
cracker.john_path = john_path
end
it 'returns the manual path if it exists and is a regular file' do
expect(::File).to receive(:file?).with(john_path).once.and_return true
expect(cracker.binary_path).to eq john_path
end
it 'rejects the manual path if it does not exist or is not a regular file' do
expect(::File).to receive(:file?).with(john_path).once.and_return false
expect(Rex::FileUtils).to receive(:find_full_path).with('john').and_return other_john_path
expect(::File).to receive(:file?).with(other_john_path).once.and_return true
expect(cracker.binary_path).to_not eq john_path
end
end
context 'when the user did not supply a path' do
it 'returns the john binary from the PATH if it exists' do
expect(Rex::FileUtils).to receive(:find_full_path).and_return john_path
expect(::File).to receive(:file?).with(john_path).once.and_return true
expect(cracker.binary_path).to eq john_path
end
it 'returns the shipped john binary if it does not exist in the PATH' do
expect(Rex::FileUtils).to receive(:find_full_path).twice.and_return nil
expect(::File).to receive(:file?).with(nil).once.and_return false
expect(cracker).to receive(:select_shipped_binary).and_return other_john_path
expect(cracker.binary_path).to eq other_john_path
end
end
end
describe '#crack_command' do
before(:each) do
expect(cracker).to receive(:binary_path).and_return john_path
expect(cracker).to receive(:john_session_id).and_return session_id
end
it 'starts with the john binary path' do
expect(cracker.crack_command[0]).to eq john_path
end
it 'sets a session id' do
expect(cracker.crack_command).to include "--session=#{session_id}"
end
it 'sets the nolog flag' do
expect(cracker.crack_command).to include '--nolog'
end
it 'adds a config directive if the user supplied one' do
cracker.config = config
expect(cracker.crack_command).to include "--config=#{config}"
end
it 'does not use a config directive if not supplied one' do
expect(cracker.crack_command).to_not include "--config=#{config}"
end
it 'uses the user supplied john.pot if there is one' do
cracker.pot = pot
expect(cracker.crack_command).to include "--pot=#{pot}"
end
it 'uses default john.pot if the user did not supply one' do
expect(cracker).to receive(:john_pot_file).and_return other_pot
expect(cracker.crack_command).to include "--pot=#{other_pot}"
end
it 'uses the user supplied format directive' do
cracker.format = nt_format
expect(cracker.crack_command).to include "--format=#{nt_format}"
end
it 'uses the user supplied wordlist directive' do
cracker.wordlist = wordlist
expect(cracker.crack_command).to include "--wordlist=#{wordlist}"
end
it 'uses the user supplied incremental directive' do
cracker.incremental = incremental
expect(cracker.crack_command).to include "--incremental=#{incremental}"
end
it 'uses the user supplied rules directive' do
cracker.rules = rules
expect(cracker.crack_command).to include "--rules=#{rules}"
end
it 'uses the user supplied max-run-time' do
cracker.max_runtime = max_runtime
expect(cracker.crack_command).to include "--max-run-time=#{max_runtime.to_s}"
end
it 'puts the path to the has file at the end' do
cracker.hash_path = hash_path
expect(cracker.crack_command.last).to eq hash_path
end
end
describe '#show_command' do
before(:each) do
expect(cracker).to receive(:binary_path).and_return john_path
end
it 'starts with the john binary path' do
expect(cracker.show_command[0]).to eq john_path
end
it 'has the --show flag' do
expect(cracker.show_command).to include '--show'
end
it 'uses the user supplied john.pot if there is one' do
cracker.pot = pot
expect(cracker.show_command).to include "--pot=#{pot}"
end
it 'uses default john.pot if the user did not supply one' do
expect(cracker).to receive(:john_pot_file).and_return other_pot
expect(cracker.show_command).to include "--pot=#{other_pot}"
end
it 'uses the user supplied format directive' do
cracker.format = nt_format
expect(cracker.show_command).to include "--format=#{nt_format}"
end
it 'puts the path to the has file at the end' do
cracker.hash_path = hash_path
expect(cracker.show_command.last).to eq hash_path
end
end
describe 'validations' do
context 'failures' do
context 'file_path validators' do
before(:each) do
expect(File).to receive(:file?).and_return false
end
it 'produces the correct error message for config' do
cracker.config = config
expect(cracker).to_not be_valid
expect(cracker.errors[:config]).to include "is not a valid path to a regular file"
end
it 'produces the correct error message for hash_path' do
cracker.hash_path = hash_path
expect(cracker).to_not be_valid
expect(cracker.errors[:hash_path]).to include "is not a valid path to a regular file"
end
it 'produces the correct error message for pot' do
cracker.pot = pot
expect(cracker).to_not be_valid
expect(cracker.errors[:pot]).to include "is not a valid path to a regular file"
end
it 'produces the correct error message for wordlist' do
cracker.wordlist = wordlist
expect(cracker).to_not be_valid
expect(cracker.errors[:wordlist]).to include "is not a valid path to a regular file"
end
end
context 'executable_path validators' do
before(:each) do
expect(File).to receive(:executable?).and_return false
end
it 'produces the correct error message for john_path' do
cracker.john_path = john_path
expect(cracker).to_not be_valid
expect(cracker.errors[:john_path]).to include "is not a valid path to an executable file"
end
end
end
context 'successes' do
context 'file_path validators' do
before(:each) do
expect(File).to receive(:file?).and_return true
end
it 'produces no error message for config' do
cracker.config = config
expect(cracker).to be_valid
expect(cracker.errors[:config]).to_not include "is not a valid path to a regular file"
end
it 'produces no error message for hash_path' do
cracker.hash_path = hash_path
expect(cracker).to be_valid
expect(cracker.errors[:hash_path]).to_not include "is not a valid path to a regular file"
end
it 'produces no error message for pot' do
cracker.pot = pot
expect(cracker).to be_valid
expect(cracker.errors[:pot]).to_not include "is not a valid path to a regular file"
end
it 'produces no error message for wordlist' do
cracker.wordlist = wordlist
expect(cracker).to be_valid
expect(cracker.errors[:wordlist]).to_not include "is not a valid path to a regular file"
end
end
context 'executable_path validators' do
before(:each) do
expect(File).to receive(:executable?).and_return true
end
it 'produces no error message for john_path' do
cracker.john_path = john_path
expect(cracker).to be_valid
expect(cracker.errors[:john_path]).to_not include "is not a valid path to an executable file"
end
end
end
end
end

View File

@ -0,0 +1,38 @@
require 'spec_helper'
require 'metasploit/framework/jtr/invalid_wordlist'
describe Metasploit::Framework::JtR::InvalidWordlist do
subject(:invalid) do
described_class.new(model)
end
let(:model) do
model_class.new
end
let(:model_class) do
Class.new do
include ActiveModel::Validations
end
end
it { should be_a StandardError }
it 'should use ActiveModel::Errors#full_messages' do
model.errors.should_receive(:full_messages).and_call_original
described_class.new(model)
end
context '#model' do
subject(:error_model) do
invalid.model
end
it 'should be the passed in model' do
error_model.should == model
end
end
end

View File

@ -0,0 +1,138 @@
require 'spec_helper'
require 'metasploit/framework/jtr/wordlist'
describe Metasploit::Framework::JtR::Wordlist do
subject(:wordlist) { described_class.new }
let(:custom_wordlist) { File.expand_path('string_list.txt',FILE_FIXTURES_PATH) }
let(:expansion_word) { 'Foo bar_baz-bat.bam\\foo//bar' }
let(:common_root_path) { File.expand_path('fake_common_roots.txt',FILE_FIXTURES_PATH) }
let(:default_wordlist_path) { File.expand_path('fake_default_wordlist.txt',FILE_FIXTURES_PATH) }
let(:password) { FactoryGirl.create(:metasploit_credential_password) }
let(:public) { FactoryGirl.create(:metasploit_credential_public) }
let(:realm) { FactoryGirl.create(:metasploit_credential_realm) }
let(:mutate_me) { 'password' }
let(:mutants) { [
"pa55word",
"password",
"pa$$word",
"passw0rd",
"pa55w0rd",
"pa$$w0rd",
"p@ssword",
"p@55word",
"p@$$word",
"p@ssw0rd",
"p@55w0rd",
"p@$$w0rd"
] }
it { should respond_to :appenders }
it { should respond_to :custom_wordlist }
it { should respond_to :mutate }
it { should respond_to :prependers }
it { should respond_to :use_common_root }
it { should respond_to :use_creds }
it { should respond_to :use_db_info }
it { should respond_to :use_default_wordlist }
it { should respond_to :use_hostnames }
describe 'validations' do
it 'raises an error if the custom_wordlist does not exist on the filesystem' do
expect(File).to receive(:file?).and_return false
wordlist.custom_wordlist = custom_wordlist
expect(wordlist).to_not be_valid
expect(wordlist.errors[:custom_wordlist]).to include "is not a valid path to a regular file"
end
it 'raises an error if mutate is not set to true or false' do
expect(wordlist).to_not be_valid
expect(wordlist.errors[:mutate]).to include "must be true or false"
end
it 'raises an error if use_common_root is not set to true or false' do
expect(wordlist).to_not be_valid
expect(wordlist.errors[:use_common_root]).to include "must be true or false"
end
it 'raises an error if use_creds is not set to true or false' do
expect(wordlist).to_not be_valid
expect(wordlist.errors[:use_creds]).to include "must be true or false"
end
it 'raises an error if use_db_info is not set to true or false' do
expect(wordlist).to_not be_valid
expect(wordlist.errors[:use_db_info]).to include "must be true or false"
end
it 'raises an error if use_default_wordlist is not set to true or false' do
expect(wordlist).to_not be_valid
expect(wordlist.errors[:use_default_wordlist]).to include "must be true or false"
end
it 'raises an error if use_hostnames is not set to true or false' do
expect(wordlist).to_not be_valid
expect(wordlist.errors[:use_hostnames]).to include "must be true or false"
end
end
describe '#valid!' do
it 'raises an InvalidWordlist exception if not valid?' do
expect{ wordlist.valid! }.to raise_error Metasploit::Framework::JtR::InvalidWordlist
end
end
describe '#expanded_words' do
it 'yields all the possible component words in the string' do
expect { |b| wordlist.expanded_words(expansion_word,&b) }.to yield_successive_args('Foo','bar','baz','bat','bam','foo','bar')
end
end
describe '#each_custom_word' do
it 'yields each word in that wordlist' do
wordlist.custom_wordlist = custom_wordlist
expect{ |b| wordlist.each_custom_word(&b) }.to yield_successive_args('foo', 'bar','baz')
end
end
describe '#each_root_word' do
it 'yields each word in the common_roots.txt list' do
expect(wordlist).to receive(:common_root_words_path).and_return common_root_path
expect { |b| wordlist.each_root_word(&b) }.to yield_successive_args('password', 'root', 'toor')
end
end
describe '#each_default_word' do
it 'yields each word in the passwords.lst list' do
expect(wordlist).to receive(:default_wordlist_path).and_return default_wordlist_path
expect { |b| wordlist.each_default_word(&b) }.to yield_successive_args('changeme', 'summer123', 'admin')
end
end
define '#each_cred_word' do
it 'yields each username,password,and realm in the database' do
expect{ |b| wordlist.each_cred_word(&b) }.to yield_successive_args(password.data, public,username, realm,value)
end
end
describe '#mutate_word' do
it 'returns an array with all possible mutations of the word' do
expect(wordlist.mutate_word(mutate_me)).to eq mutants
end
end
describe '#each_mutated_word' do
it 'yields each unique mutated word if mutate set to true' do
wordlist.mutate = true
expect { |b| wordlist.each_mutated_word(mutate_me,&b)}.to yield_successive_args(*mutants)
end
it 'yields the original word if mutate set to true' do
wordlist.mutate = false
expect { |b| wordlist.each_mutated_word(mutate_me,&b)}.to yield_with_args(mutate_me)
end
end
end