From 48f28f0aa1dad7047fea42c61fecb858e6ba6ecc Mon Sep 17 00:00:00 2001 From: Zach Goldman Date: Wed, 11 Oct 2023 13:27:33 -0500 Subject: [PATCH] add checkin validation, rest of cmd_sessions tests --- lib/msf/ui/console/command_dispatcher/core.rb | 45 ++++++++- .../console/command_dispatcher/core_spec.rb | 99 +++++++++++++++++-- 2 files changed, 131 insertions(+), 13 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index f6734a5423317..f2bfe68ab8afb 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -1842,7 +1842,15 @@ def get_matching_sessions(search_term) return end end + + unless checkin_searches.length() < 2 + unless validate_checkin_searches(checkin_searches) + return + end + end + searches = [] + unless id_searches.empty? id_matches = {} id_searches.each do |term| @@ -1852,6 +1860,7 @@ def get_matching_sessions(search_term) end searches << id_matches end + unless type_searches.empty? type_matches = {} type_searches.each do |term| @@ -1861,6 +1870,7 @@ def get_matching_sessions(search_term) end searches << type_matches end + unless checkin_searches.empty? checkin_matches = {} first_loop = true @@ -1869,13 +1879,15 @@ def get_matching_sessions(search_term) return unless matches if first_loop checkin_matches = matches + first_loop = false else checkin_matches = checkin_matches.select{ |session_id, session| matches[session_id] == session } end end searches << checkin_matches end - if searches + + unless searches.empty? matching_sessions = searches.first searches[1..].each do |result_set| matching_sessions = matching_sessions.select{| session_id, session| result_set[session_id] == session} @@ -1887,12 +1899,38 @@ def get_matching_sessions(search_term) matching_sessions end + def validate_checkin_searches(checkin_searches) + if checkin_searches.length > 2 + print_error("Too many checkin searches. Max: 2. Given: #{checkin_searches.length}") + end + valid_operators = ["before", "after"] + field1, operator1, value1 = checkin_searches[0].split(":") + field2, operator2, value2 = checkin_searches[1].split(":") + unless valid_operators.include?(operator1) && valid_operators.include?(operator2) + print_error("last_checkin can only be searched for using before or after. Ex: last_checkin:before:1m30s") + return false + end + if operator1 == operator2 + print_error("Cannot search for last_checkin with two #{operator1} arguments.") + return false + end + if (operator1 == "before" && operator2 == "after" && parse_duration(value2) < parse_duration(value1)) || (operator1 == "after" && operator2 == "before" && parse_duration(value1) < parse_duration(value2)) + print_error("After value must be a larger duration than the before value.") + return false + end + return true + end + def filter_sessions_by_search(search_term) matching_sessions = {} - field = search_term.split(":")[0] + field, operator, value = search_term.split(":") framework.sessions.each do |session_id, session| case field when "last_checkin" + unless operator == "before" || operator == "after" + print_error("Please specify before or after for checkin query. Ex: last_checkin:before:1m30s. Given: #{operator}") + return + end if session.respond_to?(:last_checkin) && session.last_checkin && evaluate_search_criteria(session, search_term) matching_sessions[session_id] = session end @@ -1920,9 +1958,6 @@ def evaluate_search_criteria(session, search_term) return checkin_time < threshold_time when "after" return checkin_time > threshold_time - else - print_error("Please specify before or after for checkin query. Ex: last_checkin:before:1m30s. Given: #{operator}") - return false end when "session_id" return session.sid.to_s == operator 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 693e891574b07..ad49a775f23cc 100644 --- a/spec/lib/msf/ui/console/command_dispatcher/core_spec.rb +++ b/spec/lib/msf/ui/console/command_dispatcher/core_spec.rb @@ -398,7 +398,6 @@ def set_tabs_test(option) end end - #TODO: restructure hierarchy, test time parsing, and add more coverage describe "#cmd_sessions" do before(:each) do allow(driver).to receive(:active_session=) @@ -432,6 +431,7 @@ def set_tabs_test(option) 1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid:1, sname: "sesh1", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel") } end + it "returns an error searching for an invalid parameter" do core.cmd_sessions("--search", "not_a_term:1") expect(@combined_output.join("\n")).to match_table <<~TABLE @@ -533,21 +533,22 @@ def set_tabs_test(option) context "searches with sessions with different checkin values" do let(:sessions) do { - 1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid:1, sname: "session1", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), - 2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.now - 90), type: 'meterpreter', sid:2, sname: "session2", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), - 3 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.now - 2000), type: 'meterpreter', sid:3, sname: "session3", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel") + 1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.parse('Dec 18, 2022 12:33:40.000000000 GMT'), type: 'meterpreter', sid:1, sname: "session1", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), + 2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.parse('Dec 18, 2022 12:33:40.000000000 GMT') - 90), type: 'meterpreter', sid:2, sname: "session2", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), + 3 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.parse('Dec 18, 2022 12:33:40.000000000 GMT') - 20000), type: 'meterpreter', sid:3, sname: "session3", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel") } end + it "filters by last checkin using fractions of a second" do - core.cmd_sessions("--search", "last_checkin:before:4.5s") + core.cmd_sessions("--search", "last_checkin:after:1m40.5s") expect(@output.join("\n")).to match_table <<~TABLE Active sessions =============== Id Name Type Information Connection -- ---- ---- ----------- ---------- + 1 session1 meterpreter info tunnel (127.0.0.1) 2 session2 meterpreter info tunnel (127.0.0.1) - 3 session3 meterpreter info tunnel (127.0.0.1) TABLE end @@ -563,13 +564,91 @@ def set_tabs_test(option) 3 session3 meterpreter info tunnel (127.0.0.1) TABLE end + + it "warns the user when searching for an unknown checkin parameter" do + core.cmd_sessions("--search", "last_checkin:something:10s") + expect(@combined_output.join("\n")).to match_table <<~TABLE + Please specify before or after for checkin query. Ex: last_checkin:before:1m30s. Given: something + TABLE + end + + it "filters by last checkin using before and after terms" do + core.cmd_sessions("--search", "last_checkin:after:200s last_checkin:before:30s") + expect(@output.join("\n")).to match_table <<~TABLE + Active sessions + =============== + + Id Name Type Information Connection + -- ---- ---- ----------- ---------- + 2 session2 meterpreter info tunnel (127.0.0.1) + TABLE + end + + it "returns no matches if after and before are swapped" do + core.cmd_sessions("--search", "last_checkin:before:200s last_checkin:after:30s") + expect(@combined_output.join("\n")).to match_table <<~TABLE + After value must be a larger duration than the before value. + TABLE + end + + it "returns a warning if middle argument is duplicated" do + core.cmd_sessions("--search", "last_checkin:before:200s last_checkin:before:30s") + expect(@combined_output.join("\n")).to match_table <<~TABLE + Cannot search for last_checkin with two before arguments. + TABLE + end + end + + context "searches with sessions that have different checkins and types" do + let(:sessions) do + { + 1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.parse('Dec 18, 2022 12:33:40.000000000 GMT'), type: 'meterpreter', sid:1, sname: "session1", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), + 2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.parse('Dec 18, 2022 12:33:40.000000000 GMT') - 90), type: 'java', sid:2, sname: "session2", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), + 3 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.parse('Dec 18, 2022 12:33:40.000000000 GMT') - 20000), type: 'cmd_shell', sid:3, sname: "session3", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel") + } + end + + it "returns results when both fields match" do + core.cmd_sessions("--search", "last_checkin:after:1m40.5s session_type:meterpreter") + expect(@output.join("\n")).to match_table <<~TABLE + Active sessions + =============== + + Id Name Type Information Connection + -- ---- ---- ----------- ---------- + 1 session1 meterpreter info tunnel (127.0.0.1) + TABLE + end + + it "does not return matches when only one field matches" do + core.cmd_sessions("--search", "last_checkin:after:1m40.5s session_type:something") + expect(@combined_output.join("\n")).to match_table <<~TABLE + No matching sessions. + TABLE + end end + + context "searches for checkin with sessions that do not respond to checkin" do + let(:sessions) do + { + 1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, type: 'meterpreter', sid:1, sname: "session1", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), + } + end + + it "returns no matches when searching against sessions without a checkin" do + core.cmd_sessions("--search", "last_checkin:after:6s") + expect(@combined_output.join("\n")).to match_table <<~TABLE + No matching sessions. + TABLE + end + end + context "with other flags" do let(:sessions) do { 1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid:1, sname: "session1", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), - 2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.now - 90), type: 'meterpreter', sid:2, sname: "session2", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), - 3 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.now - 2000), type: 'meterpreter', sid:3, sname: "session3", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel") + 2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid:2, sname: "session2", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel"), + 3 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid:3, sname: "session3", info: "info", session_host: "127.0.0.1", tunnel_to_s: "tunnel") } end it "Killall should not kill anything if nothing matches" do @@ -595,4 +674,8 @@ def set_tabs_test(option) end end end + + describe "#parse_duration" do + + end end