metasploit-framework/lib/msf/core/session.rb

435 lines
8.6 KiB
Ruby

# -*- coding: binary -*-
require 'msf/core'
module Msf
###
#
# Event notifications that affect sessions.
#
###
module SessionEvent
#
# Called when a session is opened.
#
def on_session_open(session)
end
#
# Called when a session is closed.
#
def on_session_close(session, reason='')
end
#
# Called when the user interacts with a session.
#
def on_session_interact(session)
end
#
# Called when the user writes data to a session.
#
def on_session_command(session, command)
end
#
# Called when output comes back from a user command.
#
def on_session_output(session, output)
end
#
# Called when a file is uploaded.
#
def on_session_upload(session, local_path, remote_path)
end
#
# Called when a file is downloaded.
#
def on_session_download(session, remote_path, local_path)
end
#
# Called when a file is deleted.
#
def on_session_filedelete(session, path)
end
end
###
#
# The session class represents a post-exploitation, uh, session.
# Sessions can be written to, read from, and interacted with. The
# underlying medium on which they are backed is arbitrary. For
# instance, when an exploit is provided with a command shell,
# either through a network connection or locally, the session's
# read and write operations end up reading from and writing to
# the shell that was spawned. The session object can be seen
# as a general means of interacting with various post-exploitation
# payloads through a common interface that is not necessarily
# tied to a network connection.
#
###
module Session
include Framework::Offspring
def initialize
self.alive = true
self.uuid = Rex::Text.rand_text_alphanumeric(8).downcase
@routes = RouteArray.new(self)
#self.routes = []
end
# Direct descendants
require 'msf/core/session/interactive'
require 'msf/core/session/basic'
require 'msf/core/session/comm'
# Provider interfaces
require 'msf/core/session/provider/single_command_execution'
require 'msf/core/session/provider/multi_command_execution'
require 'msf/core/session/provider/single_command_shell'
require 'msf/core/session/provider/multi_command_shell'
def self.type
"unknown"
end
#
# Returns the session's name if it's been assigned one, otherwise
# the sid is returned.
#
def name
return sname || sid
end
#
# Sets the session's name.
#
def name=(name)
self.sname = name
end
#
# Brief and to the point
#
def inspect
"#<Session:#{self.type} #{self.tunnel_peer} (#{self.session_host}) #{self.info ? "\"#{self.info.to_s}\"" : nil}>" # " Fixes highlighting
end
#
# Returns the description of the session.
#
def desc
end
#
# Returns the type of session in use.
#
def type
end
#
# Returns the local side of the tunnel.
#
def tunnel_local
end
#
# Returns the peer side of the tunnel.
#
def tunnel_peer
end
#
# Returns the host associated with the session
#
def session_host
# Prefer the overridden session host or target_host
host = @session_host || self.target_host
return host if host
# Fallback to the tunnel_peer (contains port)
peer = self.tunnel_peer
return if not peer
# Pop off the trailing port number
bits = peer.split(':')
bits.pop
bits.join(':')
end
#
# Override the host associated with this session
#
def session_host=(v)
@session_host = v
end
#
# Returns the port associated with the session
#
def session_port
port = @session_port || self.target_port
return port if port
# Fallback to the tunnel_peer (contains port)
peer = self.tunnel_peer
return if not peer
# Pop off the trailing port number
bits = peer.split(':')
port = bits.pop
port.to_i
end
#
# Override the host associated with this session
#
def session_port=(v)
@session_port = v
end
#
# Returns a pretty representation of the tunnel.
#
def tunnel_to_s
"#{(tunnel_local || '??')} -> #{(tunnel_peer || '??')}"
end
##
#
# Logging
#
##
#
# Returns the suggested name of the log file for this session.
#
def log_file_name
dt = Time.now
dstr = sprintf("%.4d%.2d%.2d", dt.year, dt.mon, dt.mday)
rhost = session_host.gsub(':', '_')
sname = name.to_s.gsub(/\W+/,'_')
"#{dstr}_#{sname}_#{rhost}_#{type}"
end
#
# Returns the log source that should be used for this session.
#
def log_source
"session_#{name}"
end
##
#
# Core interface
#
##
#
# Sets the vector through which this session was realized.
#
def set_via(opts)
self.via = opts || {}
end
#
# Configures via_payload, via_payload, workspace, target_host from an
# exploit instance. Store references from and to the exploit module.
#
def set_from_exploit(m)
self.via = { 'Exploit' => m.fullname }
self.via['Payload'] = ('payload/' + m.datastore['PAYLOAD'].to_s) if m.datastore['PAYLOAD']
self.target_host = Rex::Socket.getaddress(m.target_host) if (m.target_host.to_s.strip.length > 0)
self.target_port = m.target_port if (m.target_port.to_i != 0)
self.workspace = m.workspace
self.username = m.owner
self.exploit_datastore = m.datastore
self.user_input = m.user_input if m.user_input
self.user_output = m.user_output if m.user_output
self.exploit_uuid = m.uuid
self.exploit = m
if m[:task]
self.exploit_task = m[:task]
end
end
#
# Returns the exploit module name through which this session was
# created.
#
def via_exploit
self.via['Exploit'] if (self.via)
end
#
# Returns the payload module name through which this session was
# created.
#
def via_payload
self.via['Payload'] if (self.via)
end
#
# 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
::ActiveRecord::Base.connection_pool.with_connection {
framework.db.update_session(id: db_record.id, closed_at: Time.now.utc, close_reason: db_record.close_reason)
db_record = nil
}
end
end
#
# By default, sessions are not interactive.
#
def interactive?
false
end
#
# Allow the session to skip registration
#
def register?
true
end
#
# Allow the user to terminate this session
#
def kill
framework.sessions.deregister(self) if register?
end
def dead?
(not self.alive)
end
def alive?
(self.alive)
end
#
# Get an arch/platform combination
#
def session_type
# avoid unnecessary slash separator
if !self.arch.nil? && !self.arch.empty? && !self.platform.nil? && !self.platform.empty?
separator = '/'
else
separator = ''
end
"#{self.arch}#{separator}#{self.platform}"
end
attr_accessor :alive
#
# The framework instance that created this session.
#
attr_accessor :framework
#
# The session unique identifier.
#
attr_accessor :sid
#
# The session name.
#
attr_accessor :sname
#
# The associated workspace name
#
attr_accessor :workspace
#
# The original target host address
#
attr_accessor :target_host
#
# The original target port if applicable
#
attr_accessor :target_port
#
# The datastore of the exploit that created this session
#
attr_accessor :exploit_datastore
#
# The task that ran the exploit that got the session (that swallowed the fly)
#
attr_accessor :exploit_task
#
# The specific identified session info
#
attr_accessor :info
#
# The unique identifier of this session
#
attr_accessor :uuid
#
# The unique identifier of exploit that created this session
#
attr_accessor :exploit_uuid
#
# The unique identifier of the payload that created this session
#
attr_accessor :payload_uuid
#
# The unique machine identifier for the host that created this session
#
attr_accessor :machine_id
#
# The actual exploit module instance that created this session
#
attr_accessor :exploit
#
# The associated username
#
attr_accessor :username
#
# 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:
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