module Msf::DBManager::Session # # Returns a list of all sessions in the database that are selected # via the key-value pairs in the specified options. # def sessions(opts) return if not active ::ApplicationRecord.connection_pool.with_connection { # If we have the ID, there is no point in creating a complex query. if opts[:id] && !opts[:id].to_s.empty? return Array.wrap(Mdm::Session.find(opts[:id])) end wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework) opts = opts.clone() opts.delete(:workspace) search_term = opts.delete(:search_term) if search_term && !search_term.empty? column_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Mdm::Session, search_term) wspace.sessions.includes(:host).where(opts).where(column_search_conditions) else wspace.sessions.includes(:host).where(opts) end } end # Returns a session based on opened_time, host address, and workspace # (or returns nil) def get_session(opts) return if not active ::ApplicationRecord.connection_pool.with_connection { wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework) 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 ::Mdm::Session.find_by_host_id_and_opened_at(host.id, time) } end # @note The Mdm::Session#desc will be truncated to 255 characters. # @todo https://www.pivotaltracker.com/story/show/48249739 # # @overload report_session(opts) # Creates an Mdm::Session from Msf::Session. If +via_exploit+ is set on the # +session+, then an Mdm::Vuln and Mdm::ExploitAttempt is created for the # session's host. The Mdm::Host for the +session_host+ is created using # The session.session_host, +session.arch+ (if +session+ responds to arch), # and the workspace derived from opts or the +session+. The Mdm::Session is # assumed to be +last_seen+ and +opened_at+ at the time report_session is # called. +session.exploit_datastore['ParentModule']+ is used for the # Mdm::Session#via_exploit if +session.via_exploit+ is # 'exploit/multi/handler'. # # @param opts [Hash{Symbol => Object}] options # @option opt [Msf::Session, #datastore, #platform, #type, #via_exploit, #via_payload] :session # The in-memory session to persist to the database. # @option opts [Mdm::Workspace] :workspace The workspace for in which the # :session host is contained. Also used as the workspace for the # Mdm::ExploitAttempt and Mdm::Vuln. Defaults to Mdm::Worksapce with # Mdm::Workspace#name equal to +session.workspace+. # @return [nil] if Msf::DBManager#active is +false+. # @return [Mdm::Session] if session is saved # @raise [ArgumentError] if :session is not an {Msf::Session}. # @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be # saved, in which case, the Mdm::ExploitAttempt and Mdm::Vuln will not be # created, but the Mdm::Host will have been. (There is no transaction # to rollback the Mdm::Host creation.) # @see #find_or_create_host # @see #Msf::Util::Host.normalize_host # @see #report_exploit_success # @see #report_vuln # # @overload report_session(opts) # Creates an Mdm::Session from Mdm::Host. # # @param opts [Hash{Symbol => Object}] options # @option opts [DateTime, Time] :closed_at The date and time the session was # closed. # @option opts [String] :close_reason Reason the session was closed. # @option opts [Hash] :datastore {Msf::DataStore#to_h}. # @option opts [String] :desc Session description. Will be truncated to 255 # characters. # @option opts [Mdm::Host] :host The host on which the session was opened. # @option opts [DateTime, Time] :last_seen The last date and time the # session was seen to be open. Defaults to :closed_at's value. # @option opts [DateTime, Time] :opened_at The date and time that the # session was opened. # @option opts [String] :platform The platform of the host. # @option opts [Array] :routes ([]) The routes through the session for # pivoting. # @option opts [String] :stype Session type. # @option opts [String] :via_exploit The {Msf::Module#fullname} of the # exploit that was used to open the session. # @option option [String] :via_payload the {MSf::Module#fullname} of the # payload sent to the host when the exploit was successful. # @return [nil] if Msf::DBManager#active is +false+. # @return [Mdm::Session] if session is saved. # @raise [ArgumentError] if :host is not an Mdm::Host. # @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be # saved. # # @raise ArgumentError if :host and :session are both +nil+ def report_session(opts) return if not active ::ApplicationRecord.connection_pool.with_connection { if opts[:session] session = opts[:session] s = create_mdm_session_from_session(opts) session.db_record = s elsif opts[:host] s = create_mdm_session_from_host(opts) else raise ArgumentError.new("Missing option :session or :host") end wspace = s.workspace if session and session.via_exploit # This is a live session, we know the host is vulnerable to something. infer_vuln_from_session(session, wspace) end s } end def report_session_host_dto(host_dto) host_dto[:host] = get_host(host_dto) create_mdm_session_from_host(host_dto) end def report_session_dto(session_dto) return if not active ::ApplicationRecord.connection_pool.with_connection { host_data = session_dto[:host_data] workspace = workspaces({ name: host_data[:workspace] }).first h_opts = { host: host_data[:host], workspace: workspace } h_opts[:arch] = host_data[:arch] if !host_data[:arch].nil? && !host_data[:arch].empty? host = find_or_create_host(h_opts) session_data = session_dto[:session_data] sess_data = { datastore: session_data[:datastore], desc: session_data[:desc], host_id: host.id, last_seen: session_dto[:time_stamp], local_id: session_data[:local_id], opened_at: session_dto[:time_stamp], platform: session_data[:platform], port: session_data[:port], routes: [], stype: session_data[:stype], via_exploit: session_data[:via_exploit], via_payload: session_data[:via_payload], } if sess_data[:via_exploit] == 'exploit/multi/handler' and sess_data[:datastore] and sess_data[:datastore]['ParentModule'] sess_data[:via_exploit] = sess_data[:datastore]['ParentModule'] end session_db_record = ::Mdm::Session.create!(sess_data) # TODO: Figure out task stuff if sess_data[:via_exploit] # This is a live session, we know the host is vulnerable to something. infer_vuln_from_session_dto(session_dto, session_db_record, workspace) end session_db_record } end # Update the attributes of a session entry with the values in opts. # The values in opts should match the attributes to update. # # @param opts [Hash] Hash containing the updated values. Key should match the attribute to update. Must contain :id of record to update. # @return [Mdm::Session] The updated Mdm::Session object. def update_session(opts) return if not active ::ApplicationRecord.connection_pool.with_connection { opts = opts.clone() # protect the original caller's opts id = opts.delete(:id) session = ::Mdm::Session.find(id) session.update!(opts) return session } end # Clean out any stale sessions that have been orphaned by a dead framework instance. # @param last_seen_interval [Integer] interval, in seconds, open sessions are marked as alive def remove_stale_sessions(last_seen_interval) return unless active ::ApplicationRecord.connection_pool.with_connection { ::Mdm::Session.where(closed_at: nil).each do |db_session| next unless db_session.last_seen.nil? or ((Time.now.utc - db_session.last_seen) > (2 * last_seen_interval)) db_session.closed_at = db_session.last_seen || Time.now.utc db_session.close_reason = "Orphaned" db_session.save end } end ######### protected ######### # @param session [Msf::Session] A session with a db_record Msf::Session#db_record # @param wspace [Mdm::Workspace] # @return [void] def infer_vuln_from_session(session, wspace) ::ApplicationRecord.connection_pool.with_connection { s = session.db_record host = s.host if session.via_exploit == "exploit/multi/handler" and session.exploit_datastore['ParentModule'] mod_fullname = session.exploit_datastore['ParentModule'] else mod_fullname = session.via_exploit end mod_detail = ::Mdm::Module::Detail.find_by_fullname(mod_fullname) if mod_detail.nil? # Then the cache isn't built yet, take the hit for instantiating the # module mod_detail = framework.modules.create(mod_fullname) refs = mod_detail.references else refs = mod_detail.refs end mod_name = mod_detail.name vuln_info = { exploited_at: Time.now.utc, host: host, info: "Exploited by #{mod_fullname} to create Session #{s.id}", name: mod_name, refs: refs, workspace: wspace, } port = session.exploit_datastore["RPORT"] service = (port ? host.services.find_by_port(port.to_i) : nil) vuln_info[:service] = service if service vuln = report_vuln(vuln_info) attempt_info = { host: host, module: mod_fullname, refs: refs, service: service, session_id: s.id, timestamp: Time.now.utc, username: session.username, vuln: vuln, workspace: wspace, run_id: session.exploit.user_data.try(:[], :run_id) } report_exploit_success(attempt_info) vuln } end def create_mdm_session_from_session(opts) ::ApplicationRecord.connection_pool.with_connection { session = opts[:session] raise ArgumentError.new("Invalid :session, expected Msf::Session") unless session.kind_of? Msf::Session wspace = opts[:workspace] || find_workspace(session.workspace) h_opts = { host: Msf::Util::Host.normalize_host(session), workspace: wspace } h_opts[:arch] = session.arch if session.respond_to?(:arch) && !session.arch.nil? && !session.arch.empty? host = find_or_create_host(h_opts) sess_data = { datastore: session.exploit_datastore.to_h, desc: session.info, host_id: host.id, last_seen: Time.now.utc, local_id: session.sid, opened_at: Time.now.utc, platform: session.session_type, port: session.session_port, routes: [], stype: session.type, via_exploit: session.via_exploit, via_payload: session.via_payload, } # In the case of exploit/multi/handler we cannot yet determine the true # exploit responsible. But we can at least show the parent versus # just the generic handler: if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] sess_data[:via_exploit] = sess_data[:datastore]['ParentModule'] end s = ::Mdm::Session.create!(sess_data) if session.exploit_task and session.exploit_task.record session_task = session.exploit_task.record if session_task.class == Mdm::Task Mdm::TaskSession.create(task: session_task, session: s ) end end s } end def create_mdm_session_from_host(opts) ::ApplicationRecord.connection_pool.with_connection { host = opts[:host] raise ArgumentError.new("Invalid :host, expected Host object") unless host.kind_of? ::Mdm::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], last_seen: opts[:last_seen] || opts[:closed_at], close_reason: opts[:close_reason], } s = ::Mdm::Session.create!(sess_data) s } end def infer_vuln_from_session_dto(session_dto, session_db_record, workspace) ::ApplicationRecord.connection_pool.with_connection { vuln_info_dto = session_dto[:vuln_info] host = session_db_record.host mod_name = vuln_info_dto[:mod_name] || 'unknown' refs = vuln_info_dto[:mod_references] || [] vuln_info = { exploited_at: session_dto[:time_stamp], host: host, info: "Exploited by #{vuln_info_dto[:mod_fullname]} to create Session #{session_db_record.id}", name: mod_name, refs: refs, workspace: workspace, } port = vuln_info_dto[:remote_port] service = (port ? host.services.find_by_port(port.to_i) : nil) vuln_info[:service] = service if service vuln = report_vuln(vuln_info) attempt_info = { host: host, module: vuln_info_dto[:mod_fullname], refs: refs, service: service, session_id: session_db_record.id, timestamp: session_dto[:time_stamp], username: vuln_info_dto[:username], vuln: vuln, workspace: workspace, run_id: vuln_info_dto[:run_id] } report_exploit_success(attempt_info) vuln } end end