From 3f80f2d5306e21284d89704e0893bd3802899da9 Mon Sep 17 00:00:00 2001 From: Zach Goldman Date: Mon, 2 Oct 2023 16:27:24 -0500 Subject: [PATCH] fix time parsing, add command support fix search list, reduce duplicated code testing added --- lib/msf/base/serializer/readable_text.rb | 14 +-- lib/msf/ui/console/command_dispatcher/core.rb | 93 +++++++++++++------ .../console/command_dispatcher/core_spec.rb | 86 +++++++++++++++++ 3 files changed, 157 insertions(+), 36 deletions(-) diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb index a34d2c09e296b..73c281cb429b8 100644 --- a/lib/msf/base/serializer/readable_text.rb +++ b/lib/msf/base/serializer/readable_text.rb @@ -1,6 +1,5 @@ # -*- coding: binary -*- - module Msf module Serializer @@ -834,6 +833,7 @@ def self.dump_datastore(name, ds, indent = DefaultIndent, col = DefaultColumnWra def self.dump_sessions(framework, opts={}) output = "" verbose = opts[:verbose] || false + session_ids = opts[:session_ids] || nil show_active = opts[:show_active] || false show_inactive = opts[:show_inactive] || false # if show_active and show_inactive are false the caller didn't @@ -859,8 +859,10 @@ def self.dump_sessions(framework, opts={}) 'Header' => "Active sessions", 'Columns' => columns, 'Indent' => indent) - framework.sessions.each_sorted { |k| - session = framework.sessions[k] + + framework.sessions.each { |k| + next unless session_ids.nil? || session_ids.include?(k[0]) + session = k[1] row = create_msf_session_row(session, show_extended) tbl << row } @@ -979,7 +981,7 @@ def self.dump_sessions_verbose(framework, opts={}) out = "Active sessions\n" + "===============\n\n" - if framework.sessions.length == 0 || (opts[:search] && opts[:ids].nil?) + if framework.sessions.length == 0 out << "No active sessions.\n" return out end @@ -987,8 +989,8 @@ def self.dump_sessions_verbose(framework, opts={}) framework.sessions.each_sorted do |k| session = framework.sessions[k] - if opts[:search] - unless opts[:ids].include? session.sid + if opts[:session_ids] + unless opts[:session_ids].include? session.sid next end end diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 8ae36d1a4f0d7..4b2c315dfda5b 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -11,7 +11,6 @@ require 'msf/core/opt_condition' - require 'optparse' module Msf @@ -1448,18 +1447,14 @@ def cmd_sessions(*args) # any arguments that don't correspond to an option or option arg will # be put in here extra = [] - if args.length == 1 && args[0] =~ /-?\d+/ method = 'interact' sid = args[0].to_i else # Parse the command options - @@sessions_opts.parse(args) do |opt, idx, val| + @@sessions_opts.parse(args) do |opt, idx, val| next if has_script_arguments - # if val == "--search" || val == '-S' - # matching_sessions = filter_sessions_by_search(args[-1]) - # val = matching_sessions - # end + case opt when "-q", "--quiet" quiet = true @@ -1507,6 +1502,7 @@ def cmd_sessions(*args) # Search for specific session when "-S", "--search" search_term = val + # Display help banner when "-h", "--help" cmd_sessions_help return false @@ -1548,9 +1544,18 @@ def cmd_sessions(*args) print_error("No command specified!") return false end + if search_term + matching_sessions = get_matching_sessions(search_term) + if matching_sessions == [] + print_error("No matching sessions.") + return + end + end cmds.each do |cmd| if sid sessions = session_list + elsif matching_sessions + sessions = matching_sessions else sessions = framework.sessions.keys.sort end @@ -1661,16 +1666,19 @@ def cmd_sessions(*args) end when 'killall' if search_term - matching_sessions = [] - terms = search_term.split - terms.each do |term| - matching_sessions << filter_sessions_by_search(term) if filter_sessions_by_search(term) + matching_sessions = get_matching_sessions(search_term) + if matching_sessions == [] + print_status("No matching sessions.") + return end + print_status("Killing matching sessions...") + else + matching_sessions = framework.sessions + print_status("Killing all sessions...") end - print_status("Killing all sessions...") - framework.sessions.each_sorted.reverse_each do |s| - session = framework.sessions.get(s) - unless matching_sessions.nil? || matching_sessions.include?(session.sid.to_s) + matching_sessions.each do |s| + session = framework.sessions[s] + unless matching_sessions.nil? || matching_sessions.include?(session.sid) next end if session @@ -1774,8 +1782,15 @@ def cmd_sessions(*args) end end when 'list', 'list_inactive', nil + if search_term + matching_sessions = get_matching_sessions(search_term) + if matching_sessions == [] + print_error("No matching sessions.") + return + end + end print_line - print(Serializer::ReadableText.dump_sessions(framework, show_active: show_active, show_inactive: show_inactive, show_extended: show_extended, verbose: verbose, search_term: search_term, ids: session_list, search: search)) + print(Serializer::ReadableText.dump_sessions(framework, show_active: show_active, show_inactive: show_inactive, show_extended: show_extended, verbose: verbose, session_ids: matching_sessions)) print_line when 'name' if session_name.blank? @@ -1814,10 +1829,23 @@ def cmd_sessions(*args) true end + def get_matching_sessions(search_term) + matching_sessions = [] + terms = search_term.split + terms.each do |term| + matches = filter_sessions_by_search(term) + matches.each do |session| + matching_sessions << session.to_i if session + end + end + matching_sessions + end + def filter_sessions_by_search(search_term) matching_sessions = [] + framework.sessions.each do |session| - current_session = framework.sessions.get(session[0]) + current_session = framework.sessions[session[0]] next unless current_session case search_term.split(":")[0] when "last_checkin" @@ -1830,8 +1858,7 @@ def filter_sessions_by_search(search_term) matching_sessions << session[0].to_s if evaluate_search_criteria(current_session, search_term) end end - matching_sessions unless matching_sessions.length() == 1 - matching_sessions[0] + matching_sessions end def evaluate_search_criteria(session, search_term) @@ -1862,18 +1889,24 @@ def evaluate_search_criteria(session, search_term) end def parse_duration(duration) - case duration - when /(\d+)d/ - return $1.to_i * 86400 - when /(\d+)h/ - return $1.to_i * 3600 - when /(\d+)m/ - return $1.to_i * 60 - when /(\d+)s/ - return $1.to_i - else - return 0 + total_time = 0 + time_tokens = duration.scan(/\d+/).zip(duration.scan(/[a-zA-Z]+/)) + time_tokens.each do |pair| + raise "Please specify both time units and amounts" if pair[1].nil? + case pair[1] + when "d" + total_time = total_time + pair[0].to_i * 86400 + when "h" + total_time = total_time + pair[0].to_i * 3600 + when "m" + total_time = total_time + pair[0].to_i * 60 + when "s" + total_time = total_time + pair[0].to_i + else + raise "Unrecognized time format" + end end + return total_time end # diff --git a/spec/lib/msf/ui/console/command_dispatcher/core_spec.rb b/spec/lib/msf/ui/console/command_dispatcher/core_spec.rb index 7a8da7fd575cc..a107053c83ca9 100644 --- a/spec/lib/msf/ui/console/command_dispatcher/core_spec.rb +++ b/spec/lib/msf/ui/console/command_dispatcher/core_spec.rb @@ -397,4 +397,90 @@ def set_tabs_test(option) end end end + + describe "#cmd_sessions" do + before(:each) do + allow(driver).to receive(:active_session=) + + end + + context "with no sessions" do + it "should show an empty table" do + core.cmd_sessions + expect(@output).to eq([ + 'Active sessions', + '===============', + '', + 'No active sessions.' + ]) + end + end + + context "filtering with search results" do + before do + allow(framework).to receive(:sessions).and_return(sessions) + allow(double "Session").to receive(:kill) + end + + let(:sessions) do + { + 1 => double('Session', last_checkin: Time.now, type: 'meterpreter', sid:1, sname: "sesh1", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), + 2 => double('Session', last_checkin: (Time.now - 90), type: 'meterpreter', sid:2, sname: "sesh2", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), + 3 => double('Session', last_checkin: Time.now, type: 'java', sid:3, sname: "sesh3", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel") + } + end + + it "filters by session_id" do + core.cmd_sessions("--search", "session_id:2") + expect(@output.join("\n")).to match_table <<~TABLE + Active sessions + =============== + + Id Name Type Information Connection + -- ---- ---- ----------- ---------- + 2 sesh2 meterpreter info tunnel (127.0.0.1) + TABLE + end + + it "filters by last checkin" do + core.cmd_sessions("--search", "last_checkin:after:10s") + expect(@output.join("\n")).to match_table <<~TABLE + Active sessions + =============== + + Id Name Type Information Connection + -- ---- ---- ----------- ---------- + 2 sesh2 meterpreter info tunnel (127.0.0.1) + TABLE + end + + it "filters by session type" do + core.cmd_sessions("--search", "session_type:java") + expect(@output.join("\n")).to match_table <<~TABLE + Active sessions + =============== + + Id Name Type Information Connection + -- ---- ---- ----------- ---------- + 3 sesh3 java info tunnel (127.0.0.1) + TABLE + end + + it "Killall should not kill anything if nothing matches" do + core.cmd_sessions("--search", "session_id:5", "-K") + expect(@output).to eq([ + 'No matching sessions.' + ]) + end + + it "Killall should kill only the specified session" do + expect(sessions[2]).to receive(:kill) + core.cmd_sessions("--search", "session_id:2", "-K") + + expect(@output).to eq([ + 'Killing matching sessions...', + ]) + end + end + end end