Create a dedicated db table to track sessions & session events.
* Add new db tables for session & session_events * Migrate existing session data from events db table * Modify session report methods to log to the new tables git-svn-id: file:///home/svn/framework3/trunk@12273 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
892e241853
commit
d5d9d56081
|
@ -0,0 +1,110 @@
|
|||
class AddSessionTable < ActiveRecord::Migration
|
||||
|
||||
class Event < ActiveRecord::Base
|
||||
serialize :info
|
||||
end
|
||||
|
||||
class SessionEvent < ActiveRecord::Base
|
||||
belongs_to :session
|
||||
end
|
||||
|
||||
class Session < ActiveRecord::Base
|
||||
has_many :events, :class_name => 'AddSessionTable::SessionEvent'
|
||||
serialize :datastore
|
||||
end
|
||||
|
||||
def self.up
|
||||
|
||||
create_table :sessions do |t|
|
||||
t.integer :host_id
|
||||
|
||||
t.string :stype # session type: meterpreter, shell, etc
|
||||
t.string :via_exploit # module name
|
||||
t.string :via_payload # payload name
|
||||
t.string :desc # session description
|
||||
t.integer :port
|
||||
t.string :platform # platform type of the remote system
|
||||
t.string :routes
|
||||
|
||||
t.text :datastore # module's datastore
|
||||
|
||||
t.timestamp :opened_at, :null => false
|
||||
t.timestamp :closed_at
|
||||
|
||||
t.string :close_reason
|
||||
end
|
||||
|
||||
create_table :session_events do |t|
|
||||
t.integer :session_id
|
||||
|
||||
t.string :etype # event type: command, output, upload, download, filedelete
|
||||
t.binary :command
|
||||
t.binary :output
|
||||
t.string :remote_path
|
||||
t.string :local_path
|
||||
|
||||
t.timestamp :created_at
|
||||
end
|
||||
|
||||
#
|
||||
# Migrate session data from events table
|
||||
#
|
||||
|
||||
close_events = Event.find_all_by_name("session_close")
|
||||
open_events = Event.find_all_by_name("session_open")
|
||||
|
||||
command_events = Event.find_all_by_name("session_command")
|
||||
output_events = Event.find_all_by_name("session_output")
|
||||
upload_events = Event.find_all_by_name("session_upload")
|
||||
download_events = Event.find_all_by_name("session_download")
|
||||
|
||||
open_events.each do |o|
|
||||
c = close_events.find { |e| e.info[:session_uuid] == o.info[:session_uuid] }
|
||||
|
||||
s = Session.new(
|
||||
:host_id => o.host_id,
|
||||
:stype => o.info[:session_type],
|
||||
:via_exploit => o.info[:via_exploit],
|
||||
:via_payload => o.info[:via_payload],
|
||||
:datastore => o.info[:datastore],
|
||||
:opened_at => o.created_at
|
||||
)
|
||||
|
||||
if c
|
||||
s.closed_at = c.created_at
|
||||
s.desc = c.info[:session_info]
|
||||
else
|
||||
# couldn't find the corresponding close event
|
||||
s.closed_at = s.opened_at
|
||||
s.desc = "?"
|
||||
end
|
||||
|
||||
uuid = o.info[:session_uuid]
|
||||
|
||||
command_events.select { |e| e.info[:session_uuid] == uuid }.each do |e|
|
||||
s.events.build(:created_at => e.created_at, :etype => "command", :command => e.info[:command] )
|
||||
end
|
||||
|
||||
output_events.select { |e| e.info[:session_uuid] == uuid }.each do |e|
|
||||
s.events.build(:created_at => e.created_at, :etype => "output", :output => e.info[:output] )
|
||||
end
|
||||
|
||||
upload_events.select { |e| e.info[:session_uuid] == uuid }.each do |e|
|
||||
s.events.build(:created_at => e.created_at, :etype => "upload", :local_path => e.info[:local_path], :remote_path => e.info[:remote_path] )
|
||||
end
|
||||
|
||||
download_events.select { |e| e.info[:session_uuid] == uuid }.each do |e|
|
||||
s.events.build(:created_at => e.created_at, :etype => "download", :local_path => e.info[:local_path], :remote_path => e.info[:remote_path] )
|
||||
end
|
||||
|
||||
s.events.sort_by(&:created_at)
|
||||
|
||||
s.save!
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :sessions
|
||||
drop_table :session_events
|
||||
end
|
||||
end
|
|
@ -90,7 +90,7 @@ class CommandShell
|
|||
|
||||
# Keep reading data until no more data is available or the timeout is
|
||||
# reached.
|
||||
while (::Time.now.to_f < etime and ::IO.select([rstream], nil, nil, timeo))
|
||||
while (::Time.now.to_f < etime and (self.respond_to?(:ring) or ::IO.select([rstream], nil, nil, timeo)))
|
||||
res = shell_read(-1, 0.01)
|
||||
buff << res if res
|
||||
timeo = etime - ::Time.now.to_f
|
||||
|
@ -103,6 +103,8 @@ class CommandShell
|
|||
# Read from the command shell.
|
||||
#
|
||||
def shell_read(length=-1, timeout=1)
|
||||
return shell_read_ring(length,timeout) if self.respond_to?(:ring)
|
||||
|
||||
begin
|
||||
rv = rstream.get_once(length, timeout)
|
||||
framework.events.on_session_output(self, rv) if rv
|
||||
|
@ -114,6 +116,50 @@ class CommandShell
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Read from the command shell.
|
||||
#
|
||||
def shell_read_ring(length=-1, timeout=1)
|
||||
self.ring_buff ||= ""
|
||||
|
||||
# Short-circuit bad length values
|
||||
return "" if length == 0
|
||||
|
||||
# Return data from the stored buffer if available
|
||||
if self.ring_buff.length >= length and length > 0
|
||||
buff = self.ring_buff.slice!(0,length)
|
||||
return buff
|
||||
end
|
||||
|
||||
buff = self.ring_buff
|
||||
self.ring_buff = ""
|
||||
|
||||
begin
|
||||
::Timeout.timeout(timeout) do
|
||||
while( (length > 0 and buff.length < length) or (length == -1 and buff.length == 0))
|
||||
ring.select
|
||||
nseq,data = ring.read_data(self.ring_seq)
|
||||
if data
|
||||
self.ring_seq = nseq
|
||||
buff << data
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue ::Timeout::Error
|
||||
rescue ::Exception => e
|
||||
shell_close
|
||||
raise e
|
||||
end
|
||||
|
||||
# Store any leftovers in the ring buffer backlog
|
||||
if length > 0 and buff.length > length
|
||||
self.ring_buff = buff[length, buff.length - length]
|
||||
buff = buff[0,length]
|
||||
end
|
||||
|
||||
buff
|
||||
end
|
||||
|
||||
#
|
||||
# Writes to the command shell.
|
||||
#
|
||||
|
@ -178,7 +224,14 @@ protected
|
|||
# shell_write instead of operating on rstream directly.
|
||||
def _interact
|
||||
framework.events.on_session_interact(self)
|
||||
if self.respond_to?(:ring)
|
||||
_interact_ring
|
||||
else
|
||||
_interact_stream
|
||||
end
|
||||
end
|
||||
|
||||
def _interact_stream
|
||||
fds = [rstream.fd, user_input.fd]
|
||||
while self.interacting
|
||||
sd = Rex::ThreadSafe.select(fds, nil, fds, 0.5)
|
||||
|
@ -190,8 +243,51 @@ protected
|
|||
if sd[0].include? user_input.fd
|
||||
shell_write(user_input.gets)
|
||||
end
|
||||
Thread.pass
|
||||
end
|
||||
end
|
||||
|
||||
def _interact_ring
|
||||
|
||||
begin
|
||||
|
||||
rdr = Rex::ThreadFactory.spawn("RingMonitor", false) do
|
||||
seq = nil
|
||||
while self.interacting
|
||||
|
||||
# Look for any pending data from the remote ring
|
||||
nseq,data = ring.read_data(seq)
|
||||
|
||||
# Update the sequence number if necessary
|
||||
seq = nseq || seq
|
||||
|
||||
# Write output to the local stream if successful
|
||||
user_output.print(data) if data
|
||||
|
||||
begin
|
||||
# Wait for new data to arrive on this session
|
||||
ring.wait(seq)
|
||||
rescue EOFError => e
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
while self.interacting
|
||||
# Look for any pending input or errors from the local stream
|
||||
sd = Rex::ThreadSafe.select([ _local_fd ], nil, [_local_fd], 5.0)
|
||||
|
||||
# Write input to the ring's input mechanism
|
||||
shell_write(user_input.gets) if sd
|
||||
end
|
||||
|
||||
ensure
|
||||
rdr.kill
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :ring_seq # This tracks the last seen ring buffer sequence (for shell_read)
|
||||
attr_accessor :ring_buff # This tracks left over read data to maintain a compatible API
|
||||
end
|
||||
|
||||
class CommandShellWindows < CommandShell
|
||||
|
|
|
@ -281,8 +281,21 @@ class Meterpreter < Rex::Post::Meterpreter::Client
|
|||
::Timeout.timeout(60) do
|
||||
username = self.sys.config.getuid
|
||||
sysinfo = self.sys.config.sysinfo
|
||||
framework.db.report_note({
|
||||
:type => "host.os.session_fingerprint",
|
||||
:host => self,
|
||||
:workspace => workspace,
|
||||
:data => {
|
||||
:name => sysinfo["Computer"],
|
||||
:os => sysinfo["OS"],
|
||||
:arch => sysinfo["Architecture"],
|
||||
}
|
||||
})
|
||||
safe_info = "#{username} @ #{sysinfo['Computer']}"
|
||||
safe_info.force_encoding("ASCII-8BIT") if safe_info.respond_to?(:force_encoding)
|
||||
# Should probably be using Rex::Text.ascii_safe_hex but leave
|
||||
# this as is for now since "\xNN" is arguably uglier than "_"
|
||||
# showing up in various places in the UI.
|
||||
safe_info.gsub!(/[\x00-\x08\x0b\x0c\x0e-\x19\x7f-\xff]+/n,"_")
|
||||
self.info = safe_info
|
||||
end
|
||||
|
|
|
@ -420,6 +420,173 @@ class DBManager
|
|||
wspace.services.all(:include => :host, :conditions => conditions, :order => "hosts.address, port")
|
||||
end
|
||||
|
||||
# Returns a session based on opened_time, host address, and workspace
|
||||
# (or returns nil)
|
||||
def get_session(opts)
|
||||
return if not active
|
||||
wspace = opts[:workspace] || opts[:wspace] || workspace
|
||||
addr = opts[:addr] || opts[:address] || opts[:host] || return
|
||||
host = get_host(:workspace => wspace, :host => addr)
|
||||
time = opts[:opened_at] || opts[:created_at] || opts[:time] || return
|
||||
Msf::DBManager::Session.find_by_host_id_and_opened_at(host.id, time)
|
||||
end
|
||||
|
||||
# Record a new session in the database
|
||||
#
|
||||
# opts must contain either
|
||||
# :session - the Msf::Session object we are reporting
|
||||
# :host - the Host object we are reporting a session on.
|
||||
#
|
||||
def report_session(opts)
|
||||
return if not active
|
||||
if opts[:session]
|
||||
raise ArgumentError.new("Invalid :session, expected Msf::Session") unless opts[:session].kind_of? Msf::Session
|
||||
session = opts[:session]
|
||||
wspace = opts[:workspace] || find_workspace(session.workspace)
|
||||
h_opts = { }
|
||||
h_opts[:host] = normalize_host(session)
|
||||
h_opts[:arch] = session.arch if session.arch
|
||||
h_opts[:workspace] = wspace
|
||||
host = find_or_create_host(h_opts)
|
||||
sess_data = {
|
||||
:host_id => host.id,
|
||||
:stype => session.type,
|
||||
:desc => session.info,
|
||||
:platform => session.platform,
|
||||
:via_payload => session.via_payload,
|
||||
:via_exploit => session.via_exploit,
|
||||
:routes => [],
|
||||
:datastore => session.exploit_datastore.to_h,
|
||||
:opened_at => Time.now
|
||||
}
|
||||
elsif opts[:host]
|
||||
raise ArgumentError.new("Invalid :host, expected Host object") unless opts[:host].kind_of? Host
|
||||
host = opts[:host]
|
||||
sess_data = {
|
||||
:host_id => host.id,
|
||||
:stype => opts[:stype],
|
||||
:desc => opts[:desc],
|
||||
:platform => opts[:platform],
|
||||
:via_payload => opts[:via_payload],
|
||||
:via_exploit => opts[:via_exploit],
|
||||
:routes => opts[:routes],
|
||||
:datastore => opts[:datastore],
|
||||
:opened_at => opts[:opened_at],
|
||||
:closed_at => opts[:closed_at],
|
||||
:close_reason => opts[:close_reason],
|
||||
}
|
||||
else
|
||||
raise ArgumentError.new("Missing option :session or :host")
|
||||
end
|
||||
ret = {}
|
||||
|
||||
task = queue(Proc.new {
|
||||
s = Msf::DBManager::Session.create(sess_data)
|
||||
if opts[:session]
|
||||
session.db_record = s
|
||||
else
|
||||
myret = s.save!
|
||||
end
|
||||
ret[:session] = s
|
||||
})
|
||||
|
||||
# If this is a live session, we know the host is vulnerable to something.
|
||||
# If the exploit used was multi/handler, though, we don't know what
|
||||
# it's vulnerable to, so it isn't really useful to save it.
|
||||
if opts[:session]
|
||||
if session.via_exploit and session.via_exploit != "exploit/multi/handler"
|
||||
return unless host
|
||||
port = session.exploit_datastore["RPORT"]
|
||||
service = (port ? host.services.find_by_port(port) : nil)
|
||||
mod = framework.modules.create(session.via_exploit)
|
||||
vuln_info = {
|
||||
:host => host.address,
|
||||
:name => session.via_exploit,
|
||||
:refs => mod.references,
|
||||
:workspace => wspace
|
||||
}
|
||||
framework.db.report_vuln(vuln_info)
|
||||
# Exploit info is like vuln info, except it's /just/ for storing
|
||||
# successful exploits in an unserialized way. Yes, there is
|
||||
# duplication, but it makes exporting a score card about a
|
||||
# million times easier. TODO: See if vuln/exploit can get fixed up
|
||||
# to one useful table.
|
||||
exploit_info = {
|
||||
:name => session.via_exploit,
|
||||
:payload => session.via_payload,
|
||||
:workspace => wspace,
|
||||
:host => host,
|
||||
:service => service,
|
||||
:session_uuid => session.uuid
|
||||
}
|
||||
framework.db.report_exploit(exploit_info)
|
||||
end
|
||||
end
|
||||
|
||||
# Always wait for the task so that session.db_record gets stored
|
||||
# properly. This allows us to have session events immediately after
|
||||
# reporting the new session without having to worry about race
|
||||
# conditions.
|
||||
return nil if task.wait() != :done
|
||||
return ret[:session]
|
||||
end
|
||||
|
||||
#
|
||||
# Record a session event in the database
|
||||
#
|
||||
# opts must contain one of:
|
||||
# :session -- the Msf::Session OR the Msf::DBManager::Session we are reporting
|
||||
# :etype -- event type, enum: command, output, upload, download, filedelete
|
||||
#
|
||||
# opts may contain
|
||||
# :output -- the data for an output event
|
||||
# :command -- the data for an command event
|
||||
# :remote_path -- path to the associated file for upload, download, and filedelete events
|
||||
# :local_path -- path to the associated file for upload, and download
|
||||
#
|
||||
def report_session_event(opts)
|
||||
return if not active
|
||||
raise ArgumentError.new("Missing required option :session") if opts[:session].nil?
|
||||
raise ArgumentError.new("Expected an :etype") unless opts[:etype]
|
||||
if opts[:session].respond_to? :db_record
|
||||
session = opts[:session].db_record
|
||||
event_data = { :created_at => Time.now }
|
||||
else
|
||||
session = opts[:session]
|
||||
event_data = { :created_at => opts[:created_at] }
|
||||
end
|
||||
unless session.kind_of? Msf::DBManager::Session
|
||||
raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}")
|
||||
end
|
||||
event_data[:session_id] = session.id
|
||||
[:remote_path, :local_path, :output, :command, :etype].each do |attr|
|
||||
event_data[attr] = opts[attr] if opts[attr]
|
||||
end
|
||||
task = queue(Proc.new {
|
||||
s = Msf::DBManager::SessionEvent.create(event_data)
|
||||
})
|
||||
return task
|
||||
end
|
||||
|
||||
def report_session_route(session, route)
|
||||
return if not active
|
||||
|
||||
task = queue(Proc.new {
|
||||
session.db_record.routes << route
|
||||
session.db_record.save!
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
def report_session_route_remove(session, route)
|
||||
return if not active
|
||||
|
||||
task = queue(Proc.new {
|
||||
session.db_record.routes.delete(route)
|
||||
session.db_record.save!
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
def get_client(opts)
|
||||
wspace = opts.delete(:workspace) || workspace
|
||||
|
@ -567,6 +734,9 @@ class DBManager
|
|||
return if not active
|
||||
wait = opts.delete(:wait)
|
||||
wspace = opts.delete(:workspace) || workspace
|
||||
if wspace.kind_of? String
|
||||
wspace = find_workspace(wspace)
|
||||
end
|
||||
seen = opts.delete(:seen) || false
|
||||
crit = opts.delete(:critical) || false
|
||||
host = nil
|
||||
|
@ -576,8 +746,8 @@ class DBManager
|
|||
if opts[:host].kind_of? Host
|
||||
host = opts[:host]
|
||||
else
|
||||
report_host({:workspace => wspace, :host => opts[:host]})
|
||||
addr = normalize_host(opts[:host])
|
||||
report_host({:workspace => wspace, :host => addr})
|
||||
end
|
||||
# Do the same for a service if that's also included.
|
||||
if (opts[:port])
|
||||
|
@ -634,10 +804,10 @@ class DBManager
|
|||
conditions[:host_id] = host[:id] if host
|
||||
conditions[:service_id] = service[:id] if service
|
||||
|
||||
notes = wspace.notes.find(:all, :conditions => conditions)
|
||||
|
||||
case mode
|
||||
when :unique
|
||||
notes = wspace.notes.find(:all, :conditions => conditions)
|
||||
|
||||
# Only one note of this type should exist, make a new one if it
|
||||
# isn't there. If it is, grab it and overwrite its data.
|
||||
if notes.empty?
|
||||
|
@ -647,6 +817,8 @@ class DBManager
|
|||
end
|
||||
note.data = data
|
||||
when :unique_data
|
||||
notes = wspace.notes.find(:all, :conditions => conditions)
|
||||
|
||||
# Don't make a new Note with the same data as one that already
|
||||
# exists for the given: type and (host or service)
|
||||
notes.each do |n|
|
||||
|
@ -2742,7 +2914,7 @@ class DBManager
|
|||
end
|
||||
}
|
||||
host_address = host_data[:host].dup # Preserve after report_host() deletes
|
||||
report_host(host_data)
|
||||
report_host(host_data.merge(:wait => true))
|
||||
host.elements.each('services/service') do |service|
|
||||
service_data = {}
|
||||
service_data[:workspace] = wspace
|
||||
|
@ -2832,6 +3004,51 @@ class DBManager
|
|||
end
|
||||
report_cred(cred_data.merge(:wait => true))
|
||||
end
|
||||
|
||||
host.elements.each('sessions/session') do |sess|
|
||||
sess_id = nils_for_nulls(sess.elements["id"].text.to_s.strip.to_i)
|
||||
sess_data = {}
|
||||
sess_data[:host] = get_host(:workspace => wspace, :address => host_address)
|
||||
%W{desc platform port stype}.each {|datum|
|
||||
if sess.elements[datum].respond_to? :text
|
||||
sess_data[datum.intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip)
|
||||
end
|
||||
}
|
||||
%W{opened-at close-reason closed-at via-exploit via-payload}.each {|datum|
|
||||
if sess.elements[datum].respond_to? :text
|
||||
sess_data[datum.gsub("-","_").intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip)
|
||||
end
|
||||
}
|
||||
sess_data[:datastore] = nils_for_nulls(unserialize_object(sess.elements["datastore"], allow_yaml))
|
||||
sess_data[:routes] = nils_for_nulls(unserialize_object(sess.elements["routes"], allow_yaml))
|
||||
if not sess_data[:closed_at] # Fake a close if we don't already have one
|
||||
sess_data[:closed_at] = Time.now
|
||||
sess_data[:close_reason] = "Imported at #{Time.now}"
|
||||
end
|
||||
|
||||
existing_session = get_session(
|
||||
:workspace => sess_data[:host].workspace,
|
||||
:addr => sess_data[:host].address,
|
||||
:time => sess_data[:opened_at]
|
||||
)
|
||||
this_session = existing_session || report_session(sess_data.merge(:wait => true))
|
||||
next if existing_session
|
||||
sess.elements.each('events/event') do |sess_event|
|
||||
sess_event_data = {}
|
||||
sess_event_data[:session] = this_session
|
||||
%W{created-at etype local-path remote-path}.each {|datum|
|
||||
if sess_event.elements[datum].respond_to? :text
|
||||
sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(sess_event.elements[datum].text.to_s.strip)
|
||||
end
|
||||
}
|
||||
%W{command output}.each {|datum|
|
||||
if sess_event.elements[datum].respond_to? :text
|
||||
sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(unserialize_object(sess_event.elements[datum], allow_yaml))
|
||||
end
|
||||
}
|
||||
report_session_event(sess_event_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Import web sites
|
||||
|
@ -4468,17 +4685,56 @@ class DBManager
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Returns something suitable for the +:host+ parameter to the various report_* methods
|
||||
#
|
||||
# Takes a Host object, a Session object, an Msf::Session object or a String
|
||||
# address
|
||||
#
|
||||
def normalize_host(host)
|
||||
# If the host parameter is a Session, try to extract its address
|
||||
if host.respond_to?('target_host')
|
||||
thost = host.target_host
|
||||
tpeer = host.tunnel_peer
|
||||
if tpeer and (!thost or thost.empty?)
|
||||
thost = tpeer.split(":")[0]
|
||||
return host if host.kind_of? Host
|
||||
norm_host = nil
|
||||
|
||||
if (host.kind_of? String)
|
||||
# If it's an IPv4 addr with a host on the end, strip the port
|
||||
if host =~ /((\d{1,3}\.){3}\d{1,3}):\d+/
|
||||
norm_host = $1
|
||||
else
|
||||
norm_host = host
|
||||
end
|
||||
host = thost
|
||||
elsif host.kind_of? Session
|
||||
norm_host = host.host
|
||||
elsif host.respond_to?(:target_host)
|
||||
# Then it's an Msf::Session object with a target but target_host
|
||||
# won't be set in some cases, so try tunnel_peer as well
|
||||
thost = host.target_host
|
||||
if host.tunnel_peer and (!thost or thost.empty?)
|
||||
# tunnel_peer is of the form ip:port, so strip off the port to
|
||||
# get the addr by itself
|
||||
thost = host.tunnel_peer.split(":")[0]
|
||||
end
|
||||
norm_host = thost
|
||||
end
|
||||
host
|
||||
|
||||
# If we got here and don't have a norm_host yet, it could be a
|
||||
# Msf::Session object with an empty or nil tunnel_host and tunnel_peer;
|
||||
# see if it has a socket and use its peerhost if so.
|
||||
if (
|
||||
norm_host.nil? and
|
||||
host.respond_to?(:sock) and
|
||||
host.sock.respond_to?(:peerhost) and
|
||||
host.sock.peerhost.to_s.length > 0
|
||||
)
|
||||
norm_host = session.sock.peerhost
|
||||
end
|
||||
# If We got here and still don't have a real host, there's nothing left
|
||||
# to try, just log it and return what we were given
|
||||
if not norm_host
|
||||
dlog("Host could not be normalized: #{host.inspect}")
|
||||
norm_host = host
|
||||
end
|
||||
|
||||
norm_host
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -75,12 +75,6 @@ class Export
|
|||
return sz
|
||||
end
|
||||
|
||||
# Converts binary and whitespace characters to a hex notation.
|
||||
def ascii_safe_hex(str)
|
||||
str.gsub!(/([\x00-\x20\x80-\xFF])/){ |x| "\\x%.2x" % x.unpack("C*")[0] }
|
||||
return str
|
||||
end
|
||||
|
||||
# Formats credentials according to their type, and writes it out to the
|
||||
# supplied report file. Note for reimporting: Blank values are <BLANK>
|
||||
def write_credentials(ptype,creds,report_file)
|
||||
|
@ -112,8 +106,8 @@ class Export
|
|||
end
|
||||
else "text"
|
||||
data.each do |c|
|
||||
user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : ascii_safe_hex(c.user)
|
||||
pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : ascii_safe_hex(c.pass)
|
||||
user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : Rex::Text.ascii_safe_hex(c.user, true)
|
||||
pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : Rex::Text.ascii_safe_hex(c.pass, true)
|
||||
report_file.write "%s %s\n" % [user,pass]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -277,127 +277,60 @@ class FrameworkEventSubscriber
|
|||
#report_event(:name => "ui_start", :info => info)
|
||||
end
|
||||
|
||||
#
|
||||
# Generic handler for session events
|
||||
#
|
||||
def session_event(name, session, opts={})
|
||||
address = nil
|
||||
|
||||
if session.respond_to? :peerhost and session.peerhost.to_s.length > 0
|
||||
address = session.peerhost
|
||||
elsif session.respond_to? :tunnel_peer and session.tunnel_peer.to_s.length > 0
|
||||
address = session.tunnel_peer[0, session.tunnel_peer.rindex(":") || session.tunnel_peer.length ]
|
||||
elsif session.respond_to? :target_host and session.target_host.to_s.length > 0
|
||||
address = session.target_host
|
||||
else
|
||||
elog("Session with no peerhost/tunnel_peer")
|
||||
dlog("#{session.inspect}", LEV_3)
|
||||
return
|
||||
end
|
||||
|
||||
if framework.db.active
|
||||
ws = framework.db.find_workspace(session.workspace)
|
||||
event = {
|
||||
:workspace => ws,
|
||||
:username => session.username,
|
||||
:name => name,
|
||||
:host => address,
|
||||
:info => {
|
||||
:session_id => session.sid,
|
||||
:session_info => session.info,
|
||||
:session_uuid => session.uuid,
|
||||
:session_type => session.type,
|
||||
:username => session.username,
|
||||
:target_host => address,
|
||||
:via_exploit => session.via_exploit,
|
||||
:via_payload => session.via_payload,
|
||||
:tunnel_peer => session.tunnel_peer,
|
||||
:exploit_uuid => session.exploit_uuid
|
||||
}.merge(opts)
|
||||
}
|
||||
report_event(event)
|
||||
end
|
||||
end
|
||||
|
||||
require 'msf/core/session'
|
||||
|
||||
include ::Msf::SessionEvent
|
||||
|
||||
def on_session_open(session)
|
||||
opts = { :datastore => session.exploit_datastore.to_h, :critical => true }
|
||||
session_event('session_open', session, opts)
|
||||
if framework.db.active
|
||||
framework.db.sync
|
||||
|
||||
address = nil
|
||||
|
||||
if session.respond_to? :peerhost and session.peerhost.to_s.length > 0
|
||||
address = session.peerhost
|
||||
elsif session.respond_to? :tunnel_peer and session.tunnel_peer.to_s.length > 0
|
||||
address = session.tunnel_peer[0, session.tunnel_peer.rindex(":") || session.tunnel_peer.length ]
|
||||
elsif session.respond_to? :target_host and session.target_host.to_s.length > 0
|
||||
address = session.target_host
|
||||
else
|
||||
elog("Session with no peerhost/tunnel_peer")
|
||||
dlog("#{session.inspect}", LEV_3)
|
||||
return
|
||||
end
|
||||
|
||||
# Since we got a session, we know the host is vulnerable to something.
|
||||
# If the exploit used was multi/handler, though, we don't know what
|
||||
# it's vulnerable to, so it isn't really useful to save it.
|
||||
if session.via_exploit and session.via_exploit != "exploit/multi/handler"
|
||||
wspace = framework.db.find_workspace(session.workspace)
|
||||
host = wspace.hosts.find_by_address(address)
|
||||
return unless host
|
||||
port = session.exploit_datastore["RPORT"]
|
||||
service = (port ? host.services.find_by_port(port) : nil)
|
||||
mod = framework.modules.create(session.via_exploit)
|
||||
vuln_info = {
|
||||
:host => host.address,
|
||||
:name => session.via_exploit,
|
||||
:refs => mod.references,
|
||||
:workspace => wspace
|
||||
}
|
||||
framework.db.report_vuln(vuln_info)
|
||||
# Exploit info is like vuln info, except it's /just/ for storing
|
||||
# successful exploits in an unserialized way. Yes, there is
|
||||
# duplication, but it makes exporting a score card about a
|
||||
# million times easier. TODO: See if vuln/exploit can get fixed up
|
||||
# to one useful table.
|
||||
exploit_info = {
|
||||
:name => session.via_exploit,
|
||||
:payload => session.via_payload,
|
||||
:workspace => wspace,
|
||||
:host => host,
|
||||
:service => service,
|
||||
:session_uuid => session.uuid
|
||||
}
|
||||
ret = framework.db.report_exploit(exploit_info)
|
||||
end
|
||||
end
|
||||
framework.db.report_session(:session => session)
|
||||
end
|
||||
|
||||
def on_session_upload(session, lpath, rpath)
|
||||
session_event('session_upload', session, :local_path => lpath, :remote_path => rpath)
|
||||
framework.db.report_session_event({
|
||||
:etype => 'upload',
|
||||
:session => session,
|
||||
:local_path => lpath,
|
||||
:remote_path => rpath
|
||||
})
|
||||
end
|
||||
def on_session_download(session, rpath, lpath)
|
||||
session_event('session_download', session, :local_path => lpath, :remote_path => rpath)
|
||||
#session_event('session_download', session, :local_path => lpath, :remote_path => rpath)
|
||||
framework.db.report_session_event({
|
||||
:etype => 'download',
|
||||
:session => session,
|
||||
:local_path => lpath,
|
||||
:remote_path => rpath
|
||||
})
|
||||
end
|
||||
def on_session_filedelete(session, path)
|
||||
session_event('session_filedelete', session, :path => path)
|
||||
#session_event('session_filedelete', session, :path => path)
|
||||
framework.db.report_session_event({
|
||||
:etype => 'filedelete',
|
||||
:session => session,
|
||||
:local_path => lpath,
|
||||
:remote_path => rpath
|
||||
})
|
||||
end
|
||||
|
||||
def on_session_close(session, reason='')
|
||||
session_event('session_close', session)
|
||||
if session.db_record
|
||||
# Don't bother saving here, the session's cleanup method will take
|
||||
# care of that later.
|
||||
session.db_record.close_reason = reason
|
||||
end
|
||||
end
|
||||
|
||||
def on_session_interact(session)
|
||||
session_event('session_interact', session)
|
||||
end
|
||||
#def on_session_interact(session)
|
||||
# $stdout.puts('session_interact', session.inspect)
|
||||
#end
|
||||
|
||||
def on_session_command(session, command)
|
||||
session_event('session_command', session, :command => command)
|
||||
#session_event('session_command', session, :command => command)
|
||||
framework.db.report_session_event({
|
||||
:etype => 'command',
|
||||
:session => session,
|
||||
:command => command
|
||||
})
|
||||
end
|
||||
|
||||
def on_session_output(session, output)
|
||||
|
@ -412,10 +345,23 @@ class FrameworkEventSubscriber
|
|||
chunks << buff
|
||||
end
|
||||
chunks.each { |chunk|
|
||||
session_event('session_output', session, :output => chunk)
|
||||
#session_event('session_output', session, :output => chunk)
|
||||
framework.db.report_session_event({
|
||||
:etype => 'output',
|
||||
:session => session,
|
||||
:output => chunk
|
||||
})
|
||||
}
|
||||
end
|
||||
|
||||
def on_session_route(session, route)
|
||||
framework.db.report_session_route(session, route)
|
||||
end
|
||||
|
||||
def on_session_route_remove(session, route)
|
||||
framework.db.report_session_route_remove(session, route)
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This is covered by on_module_run and on_session_open, so don't bother
|
||||
|
|
|
@ -18,6 +18,8 @@ require 'msf/core/model/workspace'
|
|||
require 'msf/core/model/vuln'
|
||||
require 'msf/core/model/cred'
|
||||
require 'msf/core/model/exploited_host'
|
||||
require 'msf/core/model/session'
|
||||
require 'msf/core/model/session_event'
|
||||
|
||||
require 'msf/core/model/wmap_target'
|
||||
require 'msf/core/model/wmap_request'
|
||||
|
@ -35,4 +37,5 @@ require 'msf/core/model/web_vuln'
|
|||
|
||||
require 'msf/core/model/imported_cred'
|
||||
require 'msf/core/model/tag'
|
||||
require 'msf/core/model/session_event'
|
||||
|
||||
|
|
|
@ -25,6 +25,877 @@ class Host < ActiveRecord::Base
|
|||
n && n.data[:locked]
|
||||
end
|
||||
|
||||
# Determine if the fingerprint data is readable. If not, it nearly always
|
||||
# means that there was a problem with the YAML or the Marshal'ed data,
|
||||
# so let's log that for later investigation.
|
||||
def validate_fingerprint_data(fp)
|
||||
if fp.data.kind_of?(Hash) and !fp.data.empty?
|
||||
return true
|
||||
else
|
||||
dlog("Could not validate fingerprint data: #{fp.inspect}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_os
|
||||
host = self
|
||||
|
||||
wname = {} # os_name == Linux, Windows, Mac OS X, VxWorks
|
||||
wtype = {} # purpose == server, client, device
|
||||
wflav = {} # os_flavor == Ubuntu, Debian, 2003, 10.5, JetDirect
|
||||
wvers = {} # os_sp == 9.10, SP2, 10.5.3, 3.05
|
||||
warch = {} # arch == x86, PPC, SPARC, MIPS, ''
|
||||
wlang = {} # os_lang == English, ''
|
||||
whost = {} # hostname
|
||||
|
||||
# Normalize the operating system fingerprints provided by various
|
||||
# scanners (nmap, nexpose, retina, nessus, etc). These are stored as
|
||||
# notes (instead of directly in the os_* fields) specifically for this
|
||||
# purpose. Note that we're already restricting to this host by using
|
||||
# host.notes instead of Note, so don't need a host_id in the conditions.
|
||||
fingers = host.notes.find(:all,
|
||||
:conditions => [ "ntype like '%%fingerprint'" ]
|
||||
)
|
||||
fingers.each do |fp|
|
||||
next if not validate_fingerprint_data(fp)
|
||||
norm = normalize_scanner_fp(fp)
|
||||
wvers[norm[:os_sp]] = wvers[norm[:os_sp]].to_i + (100 * norm[:certainty])
|
||||
wname[norm[:os_name]] = wname[norm[:os_name]].to_i + (100 * norm[:certainty])
|
||||
wflav[norm[:os_flavor]] = wflav[norm[:os_flavor]].to_i + (100 * norm[:certainty])
|
||||
warch[norm[:arch]] = warch[norm[:arch]].to_i + (100 * norm[:certainty])
|
||||
end
|
||||
|
||||
# Grab service information and assign scores. Some services are
|
||||
# more trustworthy than others. If more services agree than not,
|
||||
# than that should be considered as well.
|
||||
# Each service has a starting number of points. Services that
|
||||
# are more difficult to fake are awarded more points. The points
|
||||
# represent a running total, not a fixed score.
|
||||
# XXX: This needs to be refactored in a big way. Tie-breaking is
|
||||
# pretty arbitrary, it would be nice to explicitly believe some
|
||||
# services over others, but that means recording which service
|
||||
# has an opinion and which doesn't. It would also be nice to
|
||||
# identify "impossible" combinations of services and alert that
|
||||
# something funny is going on.
|
||||
host.services.find(:all).each do |s|
|
||||
next if not s.info
|
||||
points = 0
|
||||
case s.name
|
||||
when 'smb'
|
||||
points = 210
|
||||
case s.info
|
||||
when /(ubuntu|debian|fedora|red ?hat|rhel)/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav[$1.capitalize] = wflav[$1.capitalize].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
when /^Windows/
|
||||
win_sp = nil
|
||||
win_flav = nil
|
||||
win_lang = nil
|
||||
|
||||
ninfo = s.info
|
||||
ninfo.gsub!('(R)', '')
|
||||
ninfo.gsub!('(TM)', '')
|
||||
ninfo.gsub!(/\s+/, ' ')
|
||||
|
||||
# Windows (R) Web Server 2008 6001 Service Pack 1 (language: Unknown) (name:PG-WIN2008WEB) (domain:WORKGROUP)
|
||||
# Windows XP Service Pack 3 (language: English) (name:EGYPT-B3E55BF3C) (domain:EGYPT-B3E55BF3C)
|
||||
# Windows 7 Ultimate (Build 7600) (language: Unknown) (name:WIN7) (domain:WORKGROUP)
|
||||
|
||||
#if ninfo =~ /^Windows ([^\s]+)(.*)(Service Pack |\(Build )([^\(]+)\(/
|
||||
if ninfo =~ /^Windows (.*)(Service Pack [^\s]+|\(Build [^\)]+\))/
|
||||
win_flav = $1.strip
|
||||
win_sp = ($2).strip
|
||||
win_sp.gsub!(/with.*/, '')
|
||||
win_sp.gsub!('Service Pack', 'SP')
|
||||
win_sp.gsub!('Build', 'b')
|
||||
win_sp.gsub!(/\s+/, '')
|
||||
win_sp.tr!("()", '')
|
||||
else
|
||||
if ninfo =~ /^Windows ([^\s+]+)([^\(]+)\(/
|
||||
win_flav = $2.strip
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if ninfo =~ /name: ([^\)]+)\)/
|
||||
hostname = $1.strip
|
||||
end
|
||||
|
||||
if ninfo =~ /language: ([^\)]+)\)/
|
||||
win_lang = $1.strip
|
||||
end
|
||||
|
||||
win_lang = nil if win_lang =~ /unknown/i
|
||||
win_vers = win_sp
|
||||
|
||||
wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
|
||||
wlang[win_lang] = wlang[win_lang].to_i + points if win_lang
|
||||
wflav[win_flav] = wflav[win_flav].to_i + points if win_flav
|
||||
wvers[win_vers] = wvers[win_vers].to_i + points if win_vers
|
||||
whost[hostname] = whost[hostname].to_i + points if hostname
|
||||
|
||||
case win_flav
|
||||
when /NT|2003|2008/
|
||||
win_type = 'server'
|
||||
else
|
||||
win_type = 'client'
|
||||
end
|
||||
wtype[win_type] = wtype[win_type].to_i + points
|
||||
end
|
||||
|
||||
when 'ssh'
|
||||
points = 104
|
||||
case s.info
|
||||
when /honeypot/i # Never trust this
|
||||
nil
|
||||
when /ubuntu/i
|
||||
# This needs to be above /debian/ becuase the ubuntu banner contains both, e.g.:
|
||||
# SSH-2.0-OpenSSH_5.3p1 Debian-3ubuntu6
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Ubuntu'] = wflav['Ubuntu'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
when /debian/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Debian'] = wflav['Debian'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
when /FreeBSD/
|
||||
wname['FreeBSD'] = wname['FreeBSD'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
when /sun_ssh/i
|
||||
wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
when /vshell|remotelyanywhere|freessh/i
|
||||
wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /radware/i
|
||||
wname['RadWare'] = wname['RadWare'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /dropbear/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /netscreen/i
|
||||
wname['NetScreen'] = wname['NetScreen'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /vpn3/
|
||||
wname['Cisco VPN 3000'] = wname['Cisco VPN 3000'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /cisco/i
|
||||
wname['Cisco IOS'] = wname['Cisco IOS'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /mpSSH/
|
||||
wname['HP iLO'] = wname['HP iLO'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
end
|
||||
when 'http'
|
||||
points = 99
|
||||
case s.info
|
||||
when /iSeries/
|
||||
wname['IBM iSeries'] = wname['IBM iSeries'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Mandrake/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Mandrake'] = wflav['Mandrake'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Mandriva/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Mandrake'] = wflav['Mandrake'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Ubuntu/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Ubuntu'] = wflav['Ubuntu'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Debian/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Debian'] = wflav['Debian'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Fedora/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Fedora'] = wflav['Fedora'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /CentOS/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['CentOS'] = wflav['CentOS'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /RHEL/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['RHEL'] = wflav['RHEL'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Red.?Hat/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Red Hat'] = wflav['Red Hat'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /SuSE/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['SUSE'] = wflav['SUSE'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /TurboLinux/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['TurboLinux'] = wflav['TurboLinux'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Gentoo/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Gentoo'] = wflav['Gentoo'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Conectiva/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Conectiva'] = wflav['Conectiva'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Asianux/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Asianux'] = wflav['Asianux'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Trustix/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Trustix'] = wflav['Trustix'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /White Box/
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['White Box'] = wflav['White Box'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /UnitedLinux/
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['UnitedLinux'] = wflav['UnitedLinux'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /PLD\/Linux/
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['PLD/Linux'] = wflav['PLD/Linux'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Vine\/Linux/
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Vine/Linux'] = wflav['Vine/Linux'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /rPath/
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['rPath'] = wflav['rPath'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /StartCom/
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['StartCom'] = wflav['StartCom'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /linux/i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /PalmOS/
|
||||
wname['PalmOS'] = wname['PalmOS'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /Microsoft[\x20\x2d]IIS\/[234]\.0/
|
||||
wname['Microsoft Windows NT 4.0'] = wname['Microsoft Windows NT 4.0'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Microsoft[\x20\x2d]IIS\/5\.0/
|
||||
wname['Microsoft Windows 2000'] = wname['Microsoft Windows 2000'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Microsoft[\x20\x2d]IIS\/5\.1/
|
||||
wname['Microsoft Windows XP'] = wname['Microsoft Windows XP'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Microsoft[\x20\x2d]IIS\/6\.0/
|
||||
wname['Microsoft Windows 2003'] = wname['Microsoft Windows 2003'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Microsoft[\x20\x2d]IIS\/7\.0/
|
||||
wname['Microsoft Windows 2008'] = wname['Microsoft Windows 2008'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Win32/i
|
||||
wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /DD\-WRT ([^\s]+) /i
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['DD-WRT'] = wflav['DD-WRT'].to_i + points
|
||||
wvers[$1.strip] = wvers[$1.strip].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /Darwin/
|
||||
wname['Apple Mac OS X'] = wname['Apple Mac OS X'].to_i + points
|
||||
|
||||
when /FreeBSD/i
|
||||
wname['FreeBSD'] = wname['FreeBSD'].to_i + points
|
||||
|
||||
when /OpenBSD/i
|
||||
wname['OpenBSD'] = wname['OpenBSD'].to_i + points
|
||||
|
||||
when /NetBSD/i
|
||||
wname['NetBSD'] = wname['NetBSD'].to_i + points
|
||||
|
||||
when /NetWare/i
|
||||
wname['Novell NetWare'] = wname['Novell NetWare'].to_i + points
|
||||
|
||||
when /OpenVMS/i
|
||||
wname['OpenVMS'] = wname['OpenVMS'].to_i + points
|
||||
|
||||
when /SunOS|Solaris/i
|
||||
wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
|
||||
|
||||
when /HP.?UX/i
|
||||
wname['HP-UX'] = wname['HP-UX'].to_i + points
|
||||
end
|
||||
when 'snmp'
|
||||
points = 103
|
||||
case s.info
|
||||
when /^Sun SNMP Agent/
|
||||
wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^SunOS ([^\s]+) ([^\s]+) /
|
||||
# XXX 1/2 XXX what does this comment mean i wonder
|
||||
wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^Linux ([^\s]+) ([^\s]+) /
|
||||
whost[$1] = whost[$1].to_i + points
|
||||
wname['Linux ' + $2] = wname['Linux ' + $2].to_i + points
|
||||
wvers[$2] = wvers[$2].to_i + points
|
||||
arch = get_arch_from_string(s.info)
|
||||
warch[arch] = warch[arch].to_i + points if arch
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^Novell NetWare ([^\s]+)/
|
||||
wname['Novell NetWare ' + $1] = wname['Novell NetWare ' + $1].to_i + points
|
||||
wvers[$1] = wvers[$1].to_i + points
|
||||
arch = "x86"
|
||||
warch[arch] = warch[arch].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^Novell UnixWare ([^\s]+)/
|
||||
wname['Novell UnixWare ' + $1] = wname['Novell UnixWare ' + $1].to_i + points
|
||||
wvers[$1] = wvers[$1].to_i + points
|
||||
arch = "x86"
|
||||
warch[arch] = warch[arch].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^HP-UX ([^\s]+) ([^\s]+) /
|
||||
# XXX
|
||||
wname['HP-UX ' + $2] = wname['HP-UX ' + $2].to_i + points
|
||||
wvers[$1] = wvers[$1].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^IBM PowerPC.*Base Operating System Runtime AIX version: (\d+\.\d+)/
|
||||
wname['IBM AIX ' + $1] = wname['IBM AIX ' + $1].to_i + points
|
||||
wvers[$1] = wvers[$1].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^SCO TCP\/IP Runtime Release ([^\s]+)/
|
||||
wname['SCO UnixWare ' + $1] = wname['SCO UnixWare ' + $1].to_i + points
|
||||
wvers[$1] = wvers[$1].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /.* IRIX version ([^\s]+)/
|
||||
wname['SGI IRIX ' + $1] = wname['SGI IRIX ' + $1].to_i + points
|
||||
wvers[$1] = wvers[$1].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^Unisys ([^\s]+) version ([^\s]+) kernel/
|
||||
wname['Unisys ' + $2] = wname['Unisys ' + $2].to_i + points
|
||||
wvers[$2] = wvers[$2].to_i + points
|
||||
whost[$1] = whost[$1].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /.*OpenVMS V([^\s]+) /
|
||||
# XXX
|
||||
wname['OpenVMS ' + $1] = wname['OpenVMS ' + $1].to_i + points
|
||||
wvers[$1] = wvers[$1].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^Hardware:.*Software: Windows NT Version ([^\s]+) /
|
||||
wname['Microsoft Windows NT ' + $1] = wname['Microsoft Windows NT ' + $1].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^Hardware:.*Software: Windows 2000 Version 5\.0/
|
||||
wname['Microsoft Windows 2000'] = wname['Microsoft Windows 2000'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^Hardware:.*Software: Windows 2000 Version 5\.1/
|
||||
wname['Microsoft Windows XP'] = wname['Microsoft Windows XP'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when /^Hardware:.*Software: Windows Version 5\.2/
|
||||
wname['Microsoft Windows 2003'] = wname['Microsoft Windows 2003'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
# XXX: TODO 2008, Vista, Windows 7
|
||||
|
||||
when /^Microsoft Windows CE Version ([^\s]+)+/
|
||||
wname['Microsoft Windows CE ' + $1] = wname['Microsoft Windows CE ' + $1].to_i + points
|
||||
wtype['client'] = wtype['client'].to_i + points
|
||||
|
||||
when /^IPSO ([^\s]+) ([^\s]+) /
|
||||
whost[$1] = whost[$1].to_i + points
|
||||
wname['Nokia IPSO ' + $2] = wname['Nokia IPSO ' + $2].to_i + points
|
||||
wvers[$2] = wvers[$2].to_i + points
|
||||
arch = get_arch_from_string(s.info)
|
||||
warch[arch] = warch[arch].to_s + points if arch
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /^Sun StorEdge/
|
||||
wname['Sun StorEdge'] = wname['Sun StorEdge'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /^HP StorageWorks/
|
||||
wname['HP StorageWorks'] = wname['HP StorageWorks'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /^Network Storage/
|
||||
# XXX
|
||||
wname['Network Storage Router'] = wname['Network Storage Router'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /Cisco Internetwork Operating System.*Version ([^\s]+)/
|
||||
vers = $1.split(/[,^\s]/)[0]
|
||||
wname['Cisco IOS ' + vers] = wname['Cisco IOS ' + vers].to_i + points
|
||||
wvers[vers] = wvers[vers].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /Cisco Catalyst.*Version ([^\s]+)/
|
||||
vers = $1.split(/[,^\s]/)[0]
|
||||
wname['Cisco CatOS ' + vers] = wname['Cisco CatOS ' + vers].to_i + points
|
||||
wvers[vers] = wvers[vers].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /Cisco 761.*Version ([^\s]+)/
|
||||
vers = $1.split(/[,^\s]/)[0]
|
||||
wname['Cisco 761 ' + vers] = wname['Cisco 761 ' + vers].to_i + points
|
||||
wvers[vers] = wvers[vers].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /Network Analysis Module.*Version ([^\s]+)/
|
||||
vers = $1.split(/[,^\s]/)[0]
|
||||
wname['Cisco NAM ' + vers] = wname['Cisco NAM ' + vers].to_i + points
|
||||
wvers[vers] = wvers[vers].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /VPN 3000 Concentrator Series Version ([^\s]+)/
|
||||
vers = $1.split(/[,^\s]/)[0]
|
||||
wname['Cisco VPN 3000 ' + vers] = wname['Cisco VPN 3000 ' + vers].to_i + points
|
||||
wvers[vers] = wvers[vers].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /ProCurve.*Switch/
|
||||
wname['3Com ProCurve Switch'] = wname['3Com ProCurve Switch'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /ProCurve.*Access Point/
|
||||
wname['3Com Access Point'] = wname['3Com Access Point'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /3Com.*Access Point/i
|
||||
wname['3Com Access Point'] = wname['3Com Access Point'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /ShoreGear/
|
||||
wname['ShoreTel Appliance'] = wname['ShoreTel Appliance'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /firewall/i
|
||||
wname['Unknown Firewall'] = wname['Unknown Firewall'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /phone/i
|
||||
wname['Unknown Phone'] = wname['Unknown Phone'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /router/i
|
||||
wname['Unknown Router'] = wname['Unknown Router'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
|
||||
when /switch/i
|
||||
wname['Unknown Switch'] = wname['Unknown Switch'].to_i + points
|
||||
wtype['device'] = wtype['device'].to_i + points
|
||||
#
|
||||
# Printer Signatures
|
||||
#
|
||||
when /^HP ETHERNET MULTI-ENVIRONMENT/
|
||||
wname['HP Printer'] = wname['HP Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Canon/i
|
||||
wname['Canon Printer'] = wname['Canon Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Epson/i
|
||||
wname['Epson Printer'] = wname['Epson Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /ExtendNet/i
|
||||
wname['ExtendNet Printer'] = wname['ExtendNet Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Fiery/i
|
||||
wname['Fiery Printer'] = wname['Fiery Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Konica/i
|
||||
wname['Konica Printer'] = wname['Konica Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Lanier/i
|
||||
wname['Lanier Printer'] = wname['Lanier Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Lantronix/i
|
||||
wname['Lantronix Printer'] = wname['Lantronix Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Lexmark/i
|
||||
wname['Lexmark Printer'] = wname['Lexmark Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Magicolor/i
|
||||
wname['Magicolor Printer'] = wname['Magicolor Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Minolta/i
|
||||
wname['Minolta Printer'] = wname['Minolta Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /NetJET/i
|
||||
wname['NetJET Printer'] = wname['NetJET Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /OKILAN/i
|
||||
wname['OKILAN Printer'] = wname['OKILAN Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Phaser/i
|
||||
wname['Phaser Printer'] = wname['Phaser Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /PocketPro/i
|
||||
wname['PocketPro Printer'] = wname['PocketPro Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Ricoh/i
|
||||
wname['Ricoh Printer'] = wname['Ricoh Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Savin/i
|
||||
wname['Savin Printer'] = wname['Savin Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /SHARP AR/i
|
||||
wname['SHARP Printer'] = wname['SHARP Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Star Micronix/i
|
||||
wname['Star Micronix Printer'] = wname['Star Micronix Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Source Tech/i
|
||||
wname['Source Tech Printer'] = wname['Source Tech Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Xerox/i
|
||||
wname['Xerox Printer'] = wname['Xerox Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /^Brother/i
|
||||
wname['Brother Printer'] = wname['Brother Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /^Axis.*Network Print/i
|
||||
wname['Axis Printer'] = wname['Axis Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /^Prestige/i
|
||||
wname['Prestige Printer'] = wname['Prestige Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /^ZebraNet/i
|
||||
wname['ZebraNet Printer'] = wname['ZebraNet Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /e\-STUDIO/i
|
||||
wname['eStudio Printer'] = wname['eStudio Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /^Gestetner/i
|
||||
wname['Gestetner Printer'] = wname['Gestetner Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /IBM.*Print/i
|
||||
wname['IBM Printer'] = wname['IBM Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /HP (Color|LaserJet|InkJet)/i
|
||||
wname['HP Printer'] = wname['HP Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Dell (Color|Laser|Ink)/i
|
||||
wname['Dell Printer'] = wname['Dell Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
when /Print/i
|
||||
wname['Unknown Printer'] = wname['Unknown Printer'].to_i + points
|
||||
wtype['printer'] = wtype['printer'].to_i + points
|
||||
end # End of s.info for SNMP
|
||||
|
||||
when 'telnet'
|
||||
points = 105
|
||||
case s.info
|
||||
when /IRIX/
|
||||
wname['SGI IRIX'] = wname['SGI IRIX'].to_i + points
|
||||
when /AIX/
|
||||
wname['IBM AIX'] = wname['IBM AIX'].to_i + points
|
||||
when /(FreeBSD|OpenBSD|NetBSD)\/(.*) /
|
||||
wname[$1] = wname[$1].to_i + points
|
||||
arch = get_arch_from_string($2)
|
||||
warch[arch] = warch[arch].to_i + points
|
||||
when /Ubuntu (\d+(\.\d+)+)/
|
||||
wname['Linux'] = wname['Linux'].to_i + points
|
||||
wflav['Ubuntu'] = wflav['Ubuntu'].to_i + points
|
||||
wvers[$1] = wvers[$1].to_i + points
|
||||
when /User Access Verification/
|
||||
wname['Cisco IOS'] = wname['Cisco IOS'].to_i + points
|
||||
when /Microsoft/
|
||||
wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
|
||||
end # End of s.info for TELNET
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
|
||||
when 'smtp'
|
||||
points = 103
|
||||
case s.info
|
||||
when /ESMTP.*SGI\.8/
|
||||
wname['SGI IRIX'] = wname['SGI IRIX'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
end # End of s.info for SMTP
|
||||
|
||||
when 'netbios'
|
||||
points = 201
|
||||
case s.info
|
||||
when /W2K3/i
|
||||
wname['Microsoft Windows 2003'] = wname['Microsoft Windows 2003'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
when /W2K8/i
|
||||
wname['Microsoft Windows 2008'] = wname['Microsoft Windows 2008'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
end # End of s.info for NETBIOS
|
||||
|
||||
when 'dns'
|
||||
points = 101
|
||||
case s.info
|
||||
when 'Microsoft DNS'
|
||||
wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
|
||||
wtype['server'] = wtype['server'].to_i + points
|
||||
end # End of s.info for DNS
|
||||
end # End of s.name case
|
||||
# End of Services
|
||||
end
|
||||
|
||||
#
|
||||
# Report the best match here
|
||||
#
|
||||
best_match = {}
|
||||
best_match[:os_name] = wname.keys.sort{|a,b| wname[b] <=> wname[a]}[0]
|
||||
best_match[:purpose] = wtype.keys.sort{|a,b| wtype[b] <=> wtype[a]}[0]
|
||||
best_match[:os_flavor] = wflav.keys.sort{|a,b| wflav[b] <=> wflav[a]}[0]
|
||||
best_match[:os_sp] = wvers.keys.sort{|a,b| wvers[b] <=> wvers[a]}[0]
|
||||
best_match[:arch] = warch.keys.sort{|a,b| warch[b] <=> warch[a]}[0]
|
||||
best_match[:name] = whost.keys.sort{|a,b| whost[b] <=> whost[a]}[0]
|
||||
best_match[:os_lang] = wlang.keys.sort{|a,b| wlang[b] <=> wlang[a]}[0]
|
||||
|
||||
best_match[:os_flavor] ||= ""
|
||||
if best_match[:os_name]
|
||||
# Handle cases where the flavor contains the base name
|
||||
best_match[:os_flavor].gsub!(best_match[:os_name], '')
|
||||
end
|
||||
|
||||
best_match[:os_name] ||= 'Unknown'
|
||||
best_match[:purpose] ||= 'device'
|
||||
|
||||
[:os_name, :purpose, :os_flavor, :os_sp, :arch, :name, :os_lang].each do |host_attr|
|
||||
next if host.attribute_locked? host_attr
|
||||
if best_match[host_attr]
|
||||
host[host_attr] = Rex::Text.ascii_safe_hex(best_match[host_attr])
|
||||
end
|
||||
end
|
||||
|
||||
host.save!
|
||||
p host
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
#
|
||||
# Convert a host.os.*_fingerprint Note into a hash containing the standard os_* fields
|
||||
#
|
||||
# Also includes a :certainty which is a float from 0 - 1.00 indicating the
|
||||
# scanner's confidence in its fingerprint. If the particular scanner does
|
||||
# not provide such information, defaults to 0.80.
|
||||
#
|
||||
def normalize_scanner_fp(fp)
|
||||
return {} if not validate_fingerprint_data(fp)
|
||||
ret = {}
|
||||
data = fp.data
|
||||
case fp.ntype
|
||||
when 'host.os.session_fingerprint'
|
||||
# These come from meterpreter sessions' client.sys.config.sysinfo
|
||||
if data[:os] =~ /Windows/
|
||||
ret.update(parse_windows_os_str(data[:os]))
|
||||
else
|
||||
ret[:os_name] = data[:os]
|
||||
end
|
||||
ret[:arch] = data[:arch] if data[:arch]
|
||||
|
||||
when 'host.os.nmap_fingerprint'
|
||||
# :os_vendor=>"Microsoft" :os_family=>"Windows" :os_version=>"2000" :os_accuracy=>"94"
|
||||
#
|
||||
# :os_match=>"Microsoft Windows Vista SP0 or SP1, Server 2008, or Windows 7 Ultimate (build 7000)"
|
||||
# :os_vendor=>"Microsoft" :os_family=>"Windows" :os_version=>"7" :os_accuracy=>"100"
|
||||
ret[:certainty] = data[:os_accuracy].to_f / 100.0
|
||||
if (data[:os_vendor] == data[:os_family])
|
||||
ret[:os_name] = data[:os_family]
|
||||
else
|
||||
ret[:os_name] = data[:os_vendor] + " " + data[:os_family]
|
||||
end
|
||||
|
||||
when 'host.os.nexpose_fingerprint'
|
||||
# :family=>"Windows" :certainty=>"0.85" :vendor=>"Microsoft" :product=>"Windows 7 Ultimate Edition"
|
||||
# :family=>"Linux" :certainty=>"0.64" :vendor=>"Linux" :product=>"Linux"
|
||||
# :family=>"Linux" :certainty=>"0.80" :vendor=>"Ubuntu" :product=>"Linux"
|
||||
# :family=>"IOS" :certainty=>"0.80" :vendor=>"Cisco" :product=>"IOS"
|
||||
# :family=>"embedded" :certainty=>"0.61" :vendor=>"Linksys" :product=>"embedded"
|
||||
ret[:certainty] = data[:certainty].to_f
|
||||
case data[:family]
|
||||
when /AIX|ESX|Mac OS X|OpenSolaris|Solaris|IOS|Linux/
|
||||
if data[:vendor] == data[:family]
|
||||
ret[:os_name] = data[:vendor]
|
||||
else
|
||||
# family often contains the vendor string, so rip it out to
|
||||
# avoid useless duplication
|
||||
ret[:os_name] = data[:vendor] + " " + data[:family].gsub(data[:vendor], '').strip
|
||||
end
|
||||
when "Windows"
|
||||
ret[:os_name] = "Microsoft Windows"
|
||||
ret[:os_flavor] = data[:product].gsub("Windows", '').strip if data[:product]
|
||||
when "embedded"
|
||||
ret[:os_name] = data[:vendor]
|
||||
else
|
||||
ret[:os_name] = data[:vendor]
|
||||
end
|
||||
ret[:arch] = get_arch_from_string(data[:arch]) if data[:arch]
|
||||
ret[:arch] ||= get_arch_from_string(data[:desc]) if data[:desc]
|
||||
|
||||
when 'host.os.retina_fingerprint'
|
||||
# :os=>"Windows Server 2003 (X64), Service Pack 2"
|
||||
case data[:os]
|
||||
when /Windows/
|
||||
ret.merge(parse_windows_os_str(data[:os]))
|
||||
else
|
||||
# No idea what this looks like if it isn't windows. Just store
|
||||
# the whole thing and hope for the best. XXX: Ghetto. =/
|
||||
ret[:os_name] = data[:os]
|
||||
end
|
||||
when 'host.os.nessus_fingerprint'
|
||||
# :os=>"Microsoft Windows 2000 Advanced Server (English)"
|
||||
# :os=>"Microsoft Windows 2000\nMicrosoft Windows XP"
|
||||
# :os=>"Linux Kernel 2.6"
|
||||
# :os=>"Sun Solaris 8"
|
||||
# :os=>"IRIX 6.5"
|
||||
|
||||
# Nessus sometimes jams multiple OS names together with a newline.
|
||||
oses = data[:os].split(/\n/)
|
||||
if oses.length > 1
|
||||
# Multiple fingerprints means Nessus wasn't really sure, reduce
|
||||
# the certainty accordingly
|
||||
ret[:certainty] = 0.5
|
||||
else
|
||||
ret[:certainty] = 0.8
|
||||
end
|
||||
|
||||
# Since there is no confidence associated with them, the best we
|
||||
# can do is just take the first one.
|
||||
case oses.first
|
||||
when /Windows/
|
||||
ret.merge(parse_windows_os_str(os))
|
||||
when /(.*)?((\d+\.)+\d+)$/
|
||||
# Then this fingerprint has some version information at the
|
||||
# end, pull it off.
|
||||
ret[:os_name] = $1.gsub("Kernel", '').strip
|
||||
ret[:os_sp] = $2
|
||||
else
|
||||
ret[:os_name] = oses.first
|
||||
end
|
||||
|
||||
when 'host.os.qualys_fingerprint'
|
||||
# :os=>"Microsoft Windows 2000"
|
||||
# :os=>"Windows 2003"
|
||||
# :os=>"Microsoft Windows XP Professional SP3"
|
||||
# :os=>"Ubuntu Linux"
|
||||
# :os=>"Cisco IOS 12.0(3)T3"
|
||||
case data[:os]
|
||||
when /Windows/
|
||||
ret.merge(parse_windows_os_str(data[:os]))
|
||||
else
|
||||
parts = data[:os].split(/\s+/, 3)
|
||||
ret[:os_name] = parts[0] + " " + parts[1]
|
||||
ret[:os_sp] = parts[2] if parts[2]
|
||||
end
|
||||
# XXX: We should really be using smb_version's stored fingerprints
|
||||
# instead of parsing the service info manually. Disable for now so we
|
||||
# don't count smb twice.
|
||||
#when 'smb.fingerprint'
|
||||
# # smb_version is kind enough to store everything we need directly
|
||||
# ret.merge(fp.data)
|
||||
# # If it's windows, this should be a pretty high-confidence
|
||||
# # fingerprint. Otherwise, it's samba which doesn't give us much of
|
||||
# # anything in most cases.
|
||||
# ret[:certainty] = 1.0 if fp.data[:os_name] =~ /Windows/
|
||||
end
|
||||
ret[:certainty] ||= 0.8
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
def parse_windows_os_str(str)
|
||||
ret = {}
|
||||
|
||||
ret[:os_name] = "Microsoft Windows"
|
||||
ret[:arch] = get_arch_from_string(str)
|
||||
if str =~ /(Service Pack|SP) ?(\d+)/
|
||||
ret[:os_sp] = "SP#{$2}"
|
||||
end
|
||||
|
||||
# Flavor
|
||||
case str
|
||||
when /(XP|2000|2003|Vista|7 .* Edition|7)/
|
||||
ret[:os_flavor] = $1
|
||||
else
|
||||
# If we couldn't pull out anything specific for the flavor, just cut
|
||||
# off the stuff we know for sure isn't it and hope for the best
|
||||
ret[:os_flavor] ||= str.gsub(/(Microsoft )?Windows|(Service Pack|SP) ?(\d+)/, '').strip
|
||||
end
|
||||
|
||||
if str =~ /NT|2003|2008|SBS|Server/
|
||||
ret[:type] = 'server'
|
||||
else
|
||||
ret[:type] = 'client'
|
||||
end
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
# A case switch to return a normalized arch based on a given string.
|
||||
def get_arch_from_string(str)
|
||||
case str
|
||||
when /x64|amd64|x86_64/i
|
||||
"x64"
|
||||
when /x86|i[3456]86/i
|
||||
"x86"
|
||||
when /PowerPC|PPC|POWER|ppc/
|
||||
"ppc"
|
||||
when /SPARC/i
|
||||
"sparc"
|
||||
when /MIPS/i
|
||||
"mips"
|
||||
when /ARM/i
|
||||
"arm"
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -8,6 +8,13 @@ class Note < ActiveRecord::Base
|
|||
belongs_to :host
|
||||
belongs_to :service
|
||||
serialize :data
|
||||
|
||||
def after_save
|
||||
if data_changed? and ntype =~ /fingerprint/
|
||||
host.normalize_os
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -15,6 +15,13 @@ class Service < ActiveRecord::Base
|
|||
has_many :web_vulns, :through => :web_sites
|
||||
|
||||
serialize :info
|
||||
|
||||
def after_save
|
||||
if info_changed?
|
||||
host.normalize_os
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
module Msf
|
||||
class DBManager
|
||||
|
||||
class Session < ActiveRecord::Base
|
||||
has_one :host
|
||||
serialize :datastore
|
||||
serialize :routes
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
module Msf
|
||||
class DBManager
|
||||
|
||||
# TODO: needs a belongs_to :session when that model gets committed.
|
||||
|
||||
class SessionEvent < ActiveRecord::Base
|
||||
include DBSave
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -36,7 +36,7 @@ class Session < Base
|
|||
authenticate(token)
|
||||
s = @framework.sessions[sid.to_i]
|
||||
if(not s)
|
||||
raise ::XMLRPC::FaultException.new(404, "unknown session")
|
||||
raise ::XMLRPC::FaultException.new(404, "unknown session while stopping")
|
||||
end
|
||||
s.kill
|
||||
{ "result" => "success" }
|
||||
|
@ -185,7 +185,7 @@ protected
|
|||
authenticate(token)
|
||||
s = @framework.sessions[sid.to_i]
|
||||
if(not s)
|
||||
raise ::XMLRPC::FaultException.new(404, "unknown session")
|
||||
raise ::XMLRPC::FaultException.new(404, "unknown session while validating")
|
||||
end
|
||||
if(s.type != type)
|
||||
raise ::XMLRPC::FaultException.new(403, "session is not "+type)
|
||||
|
|
|
@ -79,7 +79,8 @@ module Session
|
|||
def initialize
|
||||
self.alive = true
|
||||
self.uuid = Rex::Text.rand_text_alphanumeric(8).downcase
|
||||
self.routes = []
|
||||
@routes = RouteArray.new(self)
|
||||
#self.routes = []
|
||||
end
|
||||
|
||||
# Direct descendents
|
||||
|
@ -241,7 +242,16 @@ module Session
|
|||
#
|
||||
# Perform session-specific cleanup.
|
||||
#
|
||||
# NOTE: session classes overriding this method must call super!
|
||||
# Also must tolerate being called multiple times.
|
||||
#
|
||||
def cleanup
|
||||
if db_record and framework.db.active
|
||||
db_record.closed_at = Time.now
|
||||
# ignore exceptions
|
||||
db_record.save
|
||||
db_record = nil
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -262,6 +272,7 @@ module Session
|
|||
def dead?
|
||||
(not self.alive)
|
||||
end
|
||||
|
||||
def alive?
|
||||
(self.alive)
|
||||
end
|
||||
|
@ -316,6 +327,10 @@ module Session
|
|||
# An array of routes associated with this session
|
||||
#
|
||||
attr_accessor :routes
|
||||
#
|
||||
# This session's associated database record
|
||||
#
|
||||
attr_accessor :db_record
|
||||
protected
|
||||
|
||||
attr_accessor :via # :nodoc:
|
||||
|
@ -324,3 +339,21 @@ end
|
|||
|
||||
end
|
||||
|
||||
class RouteArray < Array # :nodoc: all
|
||||
def initialize(sess)
|
||||
self.session = sess
|
||||
super()
|
||||
end
|
||||
|
||||
def <<(val)
|
||||
session.framework.events.on_session_route(session, val)
|
||||
super
|
||||
end
|
||||
|
||||
def delete(val)
|
||||
session.framework.events.on_session_route_remove(session, val)
|
||||
super
|
||||
end
|
||||
|
||||
attr_accessor :session
|
||||
end
|
||||
|
|
|
@ -34,7 +34,11 @@ protected
|
|||
#
|
||||
def _interact
|
||||
framework.events.on_session_interact(self)
|
||||
interact_stream(rstream)
|
||||
if self.respond_to?(:ring)
|
||||
interact_ring(ring)
|
||||
else
|
||||
interact_stream(rstream)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'rex/ui'
|
||||
require 'rex/io/ring_buffer'
|
||||
|
||||
module Msf
|
||||
module Session
|
||||
|
@ -21,7 +22,11 @@ module Interactive
|
|||
# Initializes the session.
|
||||
#
|
||||
def initialize(rstream, opts={})
|
||||
self.rstream = rstream
|
||||
# A nil is passed in the case of non-stream interactive sessions (Meterpreter)
|
||||
if rstream
|
||||
self.rstream = rstream
|
||||
self.ring = Rex::IO::RingBuffer.new(rstream, {:size => opts[:ring_size] || 100 })
|
||||
end
|
||||
super()
|
||||
end
|
||||
|
||||
|
@ -37,10 +42,11 @@ module Interactive
|
|||
# Returns the local information.
|
||||
#
|
||||
def tunnel_local
|
||||
return @local_info if @local_info
|
||||
begin
|
||||
rstream.localinfo
|
||||
@local_info = rstream.localinfo
|
||||
rescue ::Exception
|
||||
'127.0.0.1'
|
||||
@local_info = '127.0.0.1'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -48,10 +54,11 @@ module Interactive
|
|||
# Returns the remote peer information.
|
||||
#
|
||||
def tunnel_peer
|
||||
return @peer_info if @peer_info
|
||||
begin
|
||||
@peer_info = rstream.peerinfo
|
||||
rescue ::Exception
|
||||
@peer_info ||= '127.0.0.1'
|
||||
@peer_info = '127.0.0.1'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -65,7 +72,6 @@ module Interactive
|
|||
# Terminate the session
|
||||
#
|
||||
def kill
|
||||
self.interacting = false if self.interactive?
|
||||
self.reset_ui
|
||||
self.cleanup
|
||||
super()
|
||||
|
@ -76,11 +82,13 @@ module Interactive
|
|||
#
|
||||
def cleanup
|
||||
begin
|
||||
self.interacting = false if self.interactive?
|
||||
rstream.close if (rstream)
|
||||
rescue ::Exception
|
||||
end
|
||||
|
||||
rstream = nil
|
||||
super
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -88,6 +96,11 @@ module Interactive
|
|||
#
|
||||
attr_accessor :rstream
|
||||
|
||||
#
|
||||
# The RingBuffer object used to allow concurrent access to this session
|
||||
#
|
||||
attr_accessor :ring
|
||||
|
||||
protected
|
||||
|
||||
#
|
||||
|
@ -104,10 +117,7 @@ protected
|
|||
begin
|
||||
user_want_abort?
|
||||
rescue Interrupt
|
||||
# The user hit ctrl-c while we were handling a ctrl-c, send a
|
||||
# literal ctrl-c to the shell. XXX Doesn't actually work.
|
||||
#$stdout.puts("\n[*] interrupted interrupt, sending literal ctrl-c\n")
|
||||
#$stdout.puts(run_cmd("\x03"))
|
||||
# The user hit ctrl-c while we were handling a ctrl-c. Ignore
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -21,8 +21,47 @@ class SessionManager < Hash
|
|||
self.framework = framework
|
||||
self.sid_pool = 0
|
||||
self.reaper_thread = framework.threads.spawn("SessionManager", true, self) do |manager|
|
||||
begin
|
||||
while true
|
||||
::IO.select(nil, nil, nil, 0.5)
|
||||
|
||||
rings = values.select{|s| s.respond_to?(:ring) and s.ring and s.rstream }
|
||||
ready = ::IO.select(rings.map{|s| s.rstream}, nil, nil, 0.5) || [[],[],[]]
|
||||
|
||||
ready[0].each do |fd|
|
||||
s = rings.select{|s| s.rstream == fd}.first
|
||||
next if not s
|
||||
|
||||
begin
|
||||
buff = fd.get_once(-1)
|
||||
if buff
|
||||
# Store the data in the associated ring
|
||||
s.ring.store_data(buff)
|
||||
|
||||
# Store the session event into the database.
|
||||
# Rescue anything the event handlers raise so they
|
||||
# don't break our session.
|
||||
framework.events.on_session_output(s, buff) rescue nil
|
||||
end
|
||||
rescue ::Exception => e
|
||||
wlog("Exception reading from Session #{s.sid}: #{e.class} #{e}")
|
||||
unless e.kind_of? EOFError
|
||||
# Don't bother with a call stack if it's just a
|
||||
# normal EOF
|
||||
dlog("Call Stack\n#{e.backtrace.join("\n")}", 'core', LEV_3)
|
||||
end
|
||||
|
||||
# Flush any ring data in the queue
|
||||
s.ring.clear_data rescue nil
|
||||
|
||||
# Shut down the socket itself
|
||||
s.rstream.close rescue nil
|
||||
|
||||
# Deregister the session
|
||||
manager.deregister(s, "Died from #{e.class}")
|
||||
end
|
||||
end
|
||||
|
||||
# Check for closed / dead / terminated sessions
|
||||
manager.each_value do |s|
|
||||
if not s.alive?
|
||||
manager.deregister(s, "Died")
|
||||
|
@ -31,6 +70,11 @@ class SessionManager < Hash
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
rescue ::Exception => e
|
||||
wlog("Exception in reaper thread #{e.class} #{e}")
|
||||
wlog("Call Stack\n#{e.backtrace.join("\n")}", 'core', LEV_3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -61,7 +105,13 @@ class SessionManager < Hash
|
|||
session.framework = framework
|
||||
|
||||
# Notify the framework that we have a new session opening up...
|
||||
framework.events.on_session_open(session)
|
||||
# Don't let errant event handlers kill our session
|
||||
begin
|
||||
framework.events.on_session_open(session)
|
||||
rescue ::Exception => e
|
||||
wlog("Exception in on_session_open event handler: #{e.class}: #{e}")
|
||||
wlog("Call Stack\n#{e.backtrace.join("\n")}", 'core', LEV_3)
|
||||
end
|
||||
|
||||
if session.respond_to?("console")
|
||||
session.console.on_command_proc = Proc.new { |command, error| framework.events.on_session_command(session, command) }
|
||||
|
@ -81,7 +131,7 @@ class SessionManager < Hash
|
|||
end
|
||||
|
||||
# Tell the framework that we have a parting session
|
||||
framework.events.on_session_close(session, reason)
|
||||
framework.events.on_session_close(session, reason) rescue nil
|
||||
|
||||
# If this session implements the comm interface, remove any routes
|
||||
# that have been created for it.
|
||||
|
@ -89,10 +139,6 @@ class SessionManager < Hash
|
|||
Rex::Socket::SwitchBoard.remove_by_comm(session)
|
||||
end
|
||||
|
||||
if session.kind_of?(Msf::Session::Interactive)
|
||||
session.interacting = false
|
||||
end
|
||||
|
||||
# Remove it from the hash
|
||||
self.delete(session.sid.to_i)
|
||||
|
||||
|
|
|
@ -211,7 +211,7 @@ class TaskManager
|
|||
|
||||
elog("taskmanager: task triggered an exception: #{e.class} #{e}")
|
||||
elog("taskmanager: task proc: #{task.proc.inspect} ")
|
||||
elog("taskmanager: task Call stack: #{task.source.join("\n")} ")
|
||||
elog("taskmanager: task Call stack: \n#{task.source.join("\n")} ")
|
||||
dlog("Call stack:\n#{$@.join("\n")}")
|
||||
|
||||
task.status = :dropped
|
||||
|
|
|
@ -63,7 +63,8 @@ class ThreadManager < Array
|
|||
begin
|
||||
argv.shift.call(*argv)
|
||||
rescue ::Exception => e
|
||||
elog("thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} error:#{e.class} #{e} #{e.backtrace} source:#{::Thread.current[:tm_call].inspect}")
|
||||
elog("thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} error:#{e.class} #{e} source:#{::Thread.current[:tm_call].inspect}")
|
||||
elog("Call Stack\n#{e.backtrace.join("\n")}")
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
|
|
@ -870,11 +870,12 @@ class Core
|
|||
return false
|
||||
end
|
||||
|
||||
case args.shift
|
||||
arg = args.shift
|
||||
case arg
|
||||
|
||||
when "add"
|
||||
when "add", "remove"
|
||||
if (args.length < 3)
|
||||
print_error("Missing arguments to route add.")
|
||||
print_error("Missing arguments to route #{arg}.")
|
||||
return false
|
||||
end
|
||||
|
||||
|
@ -912,56 +913,22 @@ class Core
|
|||
return false
|
||||
end
|
||||
|
||||
Rex::Socket::SwitchBoard.add_route(
|
||||
args[0],
|
||||
args[1],
|
||||
gw)
|
||||
|
||||
when "remove"
|
||||
if (args.length < 3)
|
||||
print_error("Missing arguments to route remove.")
|
||||
return false
|
||||
end
|
||||
|
||||
# Satisfy check to see that formatting is correct
|
||||
unless Rex::Socket::RangeWalker.new(args[0]).length == 1
|
||||
print_error "Invalid IP Address"
|
||||
return false
|
||||
end
|
||||
|
||||
unless Rex::Socket::RangeWalker.new(args[1]).length == 1
|
||||
print_error "Invalid Subnet mask"
|
||||
return false
|
||||
end
|
||||
|
||||
gw = nil
|
||||
|
||||
# Satisfy case problems
|
||||
args[2] = "Local" if (args[2] =~ /local/i)
|
||||
|
||||
begin
|
||||
# If the supplied gateway is a global Comm, use it.
|
||||
if (Rex::Socket::Comm.const_defined?(args[2]))
|
||||
gw = Rex::Socket::Comm.const_get(args[2])
|
||||
if arg == "remove"
|
||||
worked = Rex::Socket::SwitchBoard.remove_route(args[0], args[1], gw)
|
||||
if worked
|
||||
print_status("Route removed")
|
||||
else
|
||||
print_error("Route not found")
|
||||
end
|
||||
else
|
||||
worked = Rex::Socket::SwitchBoard.add_route(args[0], args[1], gw)
|
||||
if worked
|
||||
print_status("Route added")
|
||||
else
|
||||
print_error("Route already exists")
|
||||
end
|
||||
rescue NameError
|
||||
end
|
||||
|
||||
# If we still don't have a gateway, check if it's a session.
|
||||
if ((gw == nil) and
|
||||
(session = framework.sessions.get(args[2])) and
|
||||
(session.kind_of?(Msf::Session::Comm)))
|
||||
gw = session
|
||||
elsif (gw == nil)
|
||||
print_error("Invalid gateway specified.")
|
||||
return false
|
||||
end
|
||||
|
||||
Rex::Socket::SwitchBoard.remove_route(
|
||||
args[0],
|
||||
args[1],
|
||||
gw)
|
||||
|
||||
when "get"
|
||||
if (args.length == 0)
|
||||
print_error("You must supply an IP address.")
|
||||
|
|
|
@ -0,0 +1,364 @@
|
|||
#
|
||||
# This class implements a ring buffer with "cursors" in the form of sequence numbers.
|
||||
# To use this class, pass in a file descriptor and a ring size, the class will read
|
||||
# data from the file descriptor and store it in the ring. If the ring becomes full,
|
||||
# the oldest item will be overwritten. To emulate a stream interface, call read_data
|
||||
# to grab the last sequence number and any buffered data, call read_data again,
|
||||
# passing in the sequence number and all data newer than that sequence will be
|
||||
# returned, along with a new sequence to read from.
|
||||
#
|
||||
|
||||
require 'rex/socket'
|
||||
|
||||
module Rex
|
||||
module IO
|
||||
|
||||
class RingBuffer
|
||||
|
||||
attr_accessor :queue # The data queue, essentially an array of two-element arrays, containing a sequence and data buffer
|
||||
attr_accessor :seq # The next available sequence number
|
||||
attr_accessor :fd # The associated socket or IO object for this ring buffer
|
||||
attr_accessor :size # The number of available slots in the queue
|
||||
attr_accessor :mutex # The mutex locking access to the queue
|
||||
attr_accessor :beg # The index of the earliest data fragment in the ring
|
||||
attr_accessor :cur # The sequence number of the earliest data fragment in the ring
|
||||
attr_accessor :monitor # The thread handle of the built-in monitor when used
|
||||
attr_accessor :monitor_thread_error # :nodoc: #
|
||||
|
||||
#
|
||||
# Create a new ring buffer
|
||||
#
|
||||
def initialize(socket, opts={})
|
||||
self.size = opts[:size] || (1024 * 4)
|
||||
self.fd = socket
|
||||
self.seq = 0
|
||||
self.beg = 0
|
||||
self.cur = 0
|
||||
self.queue = Array.new( self.size )
|
||||
self.mutex = Mutex.new
|
||||
end
|
||||
|
||||
#
|
||||
# Start the built-in monitor, not called when used in a larger framework
|
||||
#
|
||||
def start_monitor
|
||||
self.monitor = monitor_thread if not self.monitor
|
||||
end
|
||||
|
||||
#
|
||||
# Stop the built-in monitor
|
||||
#
|
||||
def stop_monitor
|
||||
self.monitor.kill if self.monitor
|
||||
self.monitor = nil
|
||||
end
|
||||
|
||||
#
|
||||
# The built-in monitor thread
|
||||
#
|
||||
def monitor_thread
|
||||
Thread.new do
|
||||
begin
|
||||
while self.fd
|
||||
buff = self.fd.get_once(-1, 1.0)
|
||||
next if not buff
|
||||
store_data(buff)
|
||||
end
|
||||
rescue ::Exception => e
|
||||
self.monitor_thread_error = e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Push data back into the associated stream socket. Logging must occur
|
||||
# elsewhere, this function is simply a passthrough.
|
||||
#
|
||||
def put(data)
|
||||
self.fd.put(data)
|
||||
end
|
||||
|
||||
#
|
||||
# The clear_data method wipes the ring buffer
|
||||
#
|
||||
def clear_data
|
||||
self.mutex.synchronize do
|
||||
self.seq = 0
|
||||
self.beg = 0
|
||||
self.cur = 0
|
||||
self.queue = Array.new( self.size )
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# The store_data method is used to insert data into the ring buffer.
|
||||
#
|
||||
def store_data(data)
|
||||
self.mutex.synchronize do
|
||||
# self.cur points to the array index of queue containing the last item
|
||||
# adding data will result in cur + 1 being used to store said data
|
||||
# if cur is larger than size - 1, it will wrap back around. If cur
|
||||
# is *smaller* beg, beg is increemnted to cur + 1 (and wrapped if
|
||||
# necessary
|
||||
|
||||
loc = 0
|
||||
if self.seq > 0
|
||||
loc = ( self.cur + 1 ) % self.size
|
||||
|
||||
if loc <= self.beg
|
||||
self.beg = (self.beg + 1) % self.size
|
||||
end
|
||||
end
|
||||
|
||||
self.queue[loc] = [self.seq += 1, data]
|
||||
self.cur = loc
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# The read_data method returns a two element array with the new reader cursor (a sequence number)
|
||||
# and the returned data buffer (if any). A result of nil/nil indicates that no data is available
|
||||
#
|
||||
def read_data(ptr=nil)
|
||||
self.mutex.synchronize do
|
||||
|
||||
# Verify that there is data in the queue
|
||||
return [nil,nil] if not self.queue[self.beg]
|
||||
|
||||
# Configure the beginning read pointer (sequence number, not index)
|
||||
ptr ||= self.queue[self.beg][0]
|
||||
return [nil,nil] if not ptr
|
||||
|
||||
# If the pointer is below our baseline, we lost some data, so jump forward
|
||||
if ptr < self.queue[self.beg][0]
|
||||
ptr = self.queue[self.beg][0]
|
||||
end
|
||||
|
||||
# Calculate how many blocks exist between the current sequence number
|
||||
# and the requested pointer, this becomes the number of blocks we will
|
||||
# need to read to satisfy the result. Due to the mutex block, we do
|
||||
# not need to scan to find the sequence of the starting block or
|
||||
# check the sequence of the ending block.
|
||||
dis = self.seq - ptr
|
||||
|
||||
# If the requested sequnce number is less than our base pointer, it means
|
||||
# that no new data is available and we should return empty.
|
||||
return [nil,nil] if dis < 0
|
||||
|
||||
# Calculate the beginning block index and number of blocks to read
|
||||
off = ptr - self.queue[self.beg][0]
|
||||
set = (self.beg + off) % self.size
|
||||
|
||||
|
||||
# Build the buffer by reading forward by the number of blocks needed
|
||||
# and return the last read sequence number, plus one, as the new read
|
||||
# pointer.
|
||||
buff = ""
|
||||
cnt = 0
|
||||
lst = ptr
|
||||
ptr.upto(self.seq) do |i|
|
||||
block = self.queue[ (set + cnt) % self.size ]
|
||||
lst,data = block[0],block[1]
|
||||
buff += data
|
||||
cnt += 1
|
||||
end
|
||||
|
||||
return [lst + 1, buff]
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# The base_sequence method returns the earliest sequence number in the queue. This is zero until
|
||||
# all slots are filled and the ring rotates.
|
||||
#
|
||||
def base_sequence
|
||||
self.mutex.synchronize do
|
||||
return 0 if not self.queue[self.beg]
|
||||
return self.queue[self.beg][0]
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# The last_sequence method returns the "next" sequence number where new data will be
|
||||
# available.
|
||||
#
|
||||
def last_sequence
|
||||
self.seq
|
||||
end
|
||||
|
||||
#
|
||||
# The create_steam method assigns a IO::Socket compatible object to the ringer buffer
|
||||
#
|
||||
def create_stream
|
||||
Stream.new(self)
|
||||
end
|
||||
|
||||
#
|
||||
# The select method returns when there is a chance of new data
|
||||
# XXX: This is mostly useless and requires a rewrite to use a
|
||||
# real select or notify mechanism
|
||||
#
|
||||
def select
|
||||
::IO.select([ self.fd ], nil, [ self.fd ], 0.10)
|
||||
end
|
||||
|
||||
#
|
||||
# The wait method blocks until new data is available
|
||||
#
|
||||
def wait(seq)
|
||||
nseq = nil
|
||||
while not nseq
|
||||
nseq,data = read_data(seq)
|
||||
select
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# The wait_for method blocks until new data is available or the timeout is reached
|
||||
#
|
||||
def wait_for(seq,timeout=1)
|
||||
begin
|
||||
::Timeout.timeout(timeout) do
|
||||
wait(seq)
|
||||
end
|
||||
rescue ::Timeout::Error
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# This class provides a backwards compatible "stream" socket that uses
|
||||
# the parents ring buffer.
|
||||
#
|
||||
class Stream
|
||||
attr_accessor :ring
|
||||
attr_accessor :seq
|
||||
attr_accessor :buff
|
||||
|
||||
def initialize(ring)
|
||||
self.ring = ring
|
||||
self.seq = ring.base_sequence
|
||||
self.buff = ''
|
||||
end
|
||||
|
||||
def read(len=nil)
|
||||
if len and self.buff.length >= len
|
||||
data = self.buff.slice!(0,len)
|
||||
return data
|
||||
end
|
||||
|
||||
while true
|
||||
lseq, data = self.ring.read_data( self.seq )
|
||||
return if not lseq
|
||||
|
||||
self.seq = lseq
|
||||
self.buff << data
|
||||
if len
|
||||
if self.buff.length >= len
|
||||
return self.buff.slice!(0,len)
|
||||
else
|
||||
IO.select(nil, nil, nil, 0.25)
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
data = self.buff
|
||||
self.buff = ''
|
||||
|
||||
return data
|
||||
|
||||
# Not reached
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def write(data)
|
||||
self.ring.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
server = Rex::Socket.create_tcp_server('LocalPort' => 0)
|
||||
lport = server.getsockname[2]
|
||||
client = Rex::Socket.create_tcp('PeerHost' => '127.0.0.1', 'PeerPort' => lport)
|
||||
conn = server.accept
|
||||
|
||||
r = Rex::IO::RingBuffer.new(conn, {:size => 1024*1024})
|
||||
client.put("1")
|
||||
client.put("2")
|
||||
client.put("3")
|
||||
|
||||
s,d = r.read_data
|
||||
|
||||
client.put("4")
|
||||
client.put("5")
|
||||
client.put("6")
|
||||
s,d = r.read_data(s)
|
||||
|
||||
client.put("7")
|
||||
client.put("8")
|
||||
client.put("9")
|
||||
s,d = r.read_data(s)
|
||||
|
||||
client.put("0")
|
||||
s,d = r.read_data(s)
|
||||
|
||||
test_counter = 11
|
||||
1.upto(100) do
|
||||
client.put( "X" )
|
||||
test_counter += 1
|
||||
end
|
||||
|
||||
sleep(1)
|
||||
|
||||
s,d = r.read_data
|
||||
p s
|
||||
p d
|
||||
|
||||
fdata = ''
|
||||
File.open("/bin/ls", "rb") do |fd|
|
||||
fdata = fd.read(fd.stat.size)
|
||||
fdata = fdata * 10
|
||||
client.put(fdata)
|
||||
end
|
||||
|
||||
sleep(1)
|
||||
|
||||
s,vdata = r.read_data(s)
|
||||
|
||||
if vdata != fdata
|
||||
puts "DATA FAILED"
|
||||
else
|
||||
puts "DATA VERIFIED"
|
||||
end
|
||||
|
||||
r.clear_data
|
||||
|
||||
a = r.create_stream
|
||||
b = r.create_stream
|
||||
|
||||
client.put("ABC123")
|
||||
sleep(1)
|
||||
|
||||
p a.read
|
||||
p b.read
|
||||
|
||||
client.put("$$$$$$")
|
||||
sleep(1)
|
||||
|
||||
p a.read
|
||||
p b.read
|
||||
|
||||
c = r.create_stream
|
||||
p c.read
|
||||
|
||||
=end
|
||||
|
||||
|
|
@ -597,6 +597,20 @@ module Text
|
|||
[ str.downcase.gsub(/'/,'').gsub(/\\?x([a-f0-9][a-f0-9])/, '\1') ].pack("H*")
|
||||
end
|
||||
|
||||
#
|
||||
# Turn non-printable chars into hex representations, leaving others alone
|
||||
#
|
||||
# If +whitespace+ is true, converts whitespace (0x20, 0x09, etc) to hex as
|
||||
# well.
|
||||
#
|
||||
def self.ascii_safe_hex(str, whitespace=false)
|
||||
if whitespace
|
||||
str.gsub(/([\x00-\x20\x80-\xFF])/){ |x| "\\x%.2x" % x.unpack("C*")[0] }
|
||||
else
|
||||
str.gsub(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xFF])/n){ |x| "\\x%.2x" % x.unpack("C*")[0]}
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Wraps text at a given column using a supplied indention
|
||||
#
|
||||
|
|
|
@ -199,6 +199,50 @@ protected
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Interacts between a local stream and a remote ring buffer. This has to use
|
||||
# a secondary thread to prevent the select on the local stream from blocking
|
||||
#
|
||||
def interact_ring(ring)
|
||||
begin
|
||||
|
||||
rdr = Rex::ThreadFactory.spawn("RingMonitor", false) do
|
||||
seq = nil
|
||||
while self.interacting
|
||||
|
||||
# Look for any pending data from the remote ring
|
||||
nseq,data = ring.read_data(seq)
|
||||
|
||||
# Update the sequence number if necessary
|
||||
seq = nseq || seq
|
||||
|
||||
# Write output to the local stream if successful
|
||||
user_output.print(data) if data
|
||||
|
||||
# Wait for new data to arrive on this session
|
||||
ring.wait(seq)
|
||||
end
|
||||
end
|
||||
|
||||
while self.interacting
|
||||
|
||||
# Look for any pending input from the local stream
|
||||
sd = Rex::ThreadSafe.select([ _local_fd ], nil, [_local_fd], 5.0)
|
||||
|
||||
# Write input to the ring's input mechanism
|
||||
if sd
|
||||
data = user_input.gets
|
||||
ring.put(data)
|
||||
end
|
||||
end
|
||||
|
||||
ensure
|
||||
rdr.kill
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Installs a signal handler to monitor suspend signal notifications.
|
||||
#
|
||||
|
|
|
@ -801,14 +801,20 @@ class Metasploit3 < Msf::Auxiliary
|
|||
(os_name, os_flavor, os_sp, os_lang, arch, ua_name, ua_ver) = detected_version.split(':')
|
||||
|
||||
if framework.db.active
|
||||
host_info = { :host => cli.peerhost }
|
||||
host_info[:os_name] = os_name if os_name != "undefined"
|
||||
host_info[:os_flavor] = os_flavor if os_flavor != "undefined"
|
||||
host_info[:os_sp] = os_sp if os_sp != "undefined"
|
||||
host_info[:os_lang] = os_lang if os_lang != "undefined"
|
||||
host_info[:arch] = arch if arch != "undefined"
|
||||
note_data = { }
|
||||
note_data[:os_name] = os_name if os_name != "undefined"
|
||||
note_data[:os_flavor] = os_flavor if os_flavor != "undefined"
|
||||
note_data[:os_sp] = os_sp if os_sp != "undefined"
|
||||
note_data[:os_lang] = os_lang if os_lang != "undefined"
|
||||
note_data[:arch] = arch if arch != "undefined"
|
||||
print_status("Reporting: #{note_data.inspect}")
|
||||
|
||||
report_host(host_info)
|
||||
report_note({
|
||||
:host => cli.peerhost,
|
||||
:type => 'javascript_fingerprint',
|
||||
:data => note_data,
|
||||
:update => :unique_data,
|
||||
})
|
||||
client_info = ({
|
||||
:host => cli.peerhost,
|
||||
:ua_string => request['User-Agent'],
|
||||
|
|
Loading…
Reference in New Issue