diff --git a/lib/msf/core/exploit/local/persistence.rb b/lib/msf/core/exploit/local/persistence.rb new file mode 100644 index 0000000000000..3f54b7760d22b --- /dev/null +++ b/lib/msf/core/exploit/local/persistence.rb @@ -0,0 +1,74 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Local::Persistence + def initialize(info = {}) + @persistence_service = Rex::Sync::Event.new(auto_reset=false) + @clean_up_rc = nil + super( + update_info( + info, + 'DefaultOptions' => {}, + # https://github.com/rapid7/metasploit-framework/pull/19676#discussion_r1907594308 + 'Stance' => Msf::Exploit::Stance::Passive, + 'Passive' => true + ) + ) + + register_advanced_options( + [ + OptString.new('WritableDir', [true, 'A directory where we can write files', '']), + OptBool.new('CleanUpRc', [true, 'Create a cleanup resource file.', true]) + ] + ) + end + + def exploit + run_as_background = !datastore['DisablePayloadHandler'] + print_warning('Payload handler is disabled, the persistence will be installed only.') unless run_as_background + + # Call the install_persistence function + # must be declared inside the persistence module + install_persistence + + save_cleanup_rc if datastore['CleanUpRc'] && !@clean_up_rc.empty? + + @persistence_service.wait if run_as_background + end + + def install_persistence + # to be overloaded by the module + end + + def save_cleanup_rc + host = session.sys.config.sysinfo['Computer'] + # Create Filename info to be appended to downloaded files + filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') + logs = ::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo)) + # Create the log directory + ::FileUtils.mkdir_p(logs) + + # logfile name + clean_rc = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc' + file_local_write(clean_rc, @clean_up_rc) + + print_status("Meterpreter-compatible Cleaup RC file: #{clean_rc}") + + report_note(host: host, + type: 'host.persistance.cleanup', + data: { + local_id: session.sid, + stype: session.type, + desc: session.info, + platform: session.platform, + via_payload: session.via_payload, + via_exploit: session.via_exploit, + created_at: Time.now.utc, + commands: @clean_up_rc + }) + end + + def cleanup + end + end +end \ No newline at end of file diff --git a/lib/msf/core/exploit/local/timespec.rb b/lib/msf/core/exploit/local/timespec.rb new file mode 100644 index 0000000000000..4703fb4200ae5 --- /dev/null +++ b/lib/msf/core/exploit/local/timespec.rb @@ -0,0 +1,28 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Local::Timespec + TIMESPEC_REGEX = %r{ + \b( + (?:[01]?\d|2[0-3]):[0-5]\d(?:\s?(?:AM|PM))? | # Matches HH:MM (12h/24h) + midnight | noon | teatime | now | # Matches special keywords + now\s?\+\s?\d+\s?(?:minutes?|hours?|days?|weeks?) | # Matches relative times + (?:mon|tue|wed|thu|fri|sat|sun)(?:day)? | # Matches named days + (?:next|last)\s(?:mon|tue|wed|thu|fri|sat|sun)(?:day)? | # Matches next/last weekday + \d{1,2}/\d{1,2}/\d{2,4} | # Matches MM/DD/YY(YY) + \d{1,2}\.\d{1,2}\.\d{2,4} | # Matches DD.MM.YY(YY) + \d{6} | \d{8} # Matches MMDDYY or MMDDYYYY + )\b + }xi # 'x' allows extended mode, 'i' makes it case-insensitive + + # + # Attempts to validate a timespec. + # + # @param timespec [String] The timespec to test + # @return [Boolean] If the timespec is valid or not + # + def self.valid_timespec?(timespec) + !!(timespec =~ TIMESPEC_REGEX) # Ensures true/false return + end + end +end \ No newline at end of file diff --git a/lib/msf/core/post/linux/user.rb b/lib/msf/core/post/linux/user.rb new file mode 100644 index 0000000000000..3999b90d7c2e6 --- /dev/null +++ b/lib/msf/core/post/linux/user.rb @@ -0,0 +1,22 @@ +# -*- coding: binary -*- + +module Msf + class Post + module Linux + module User + include ::Msf::Post::Common + # + # Returns a string of the user's home directory + # + def get_home_dir(user) + cmd_exec("grep '^#{user}:' /etc/passwd | cut -d ':' -f 6").chomp + # could also be: "getent passwd #{user} | cut -d: -f6" + end + # User + end + # Linux + end + # Post + end + # Msf +end \ No newline at end of file diff --git a/spec/lib/msf/core/exploit/local/timespec_spec.rb b/spec/lib/msf/core/exploit/local/timespec_spec.rb new file mode 100644 index 0000000000000..732dcb424eb8a --- /dev/null +++ b/spec/lib/msf/core/exploit/local/timespec_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +RSpec.describe Msf::Exploit::Local::Timespec do + describe '.valid_timespec?' do + it 'returns true for military time' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('14:30')).to eq(true) + end + + it 'returns true for 12hr time' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('2:15 PM')).to eq(true) + end + + it 'returns true for midnight' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('midnight')).to eq(true) + end + + it 'returns true for now' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('now')).to eq(true) + end + + it 'returns true for now plus time' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('now + 10 minutes')).to eq(true) + end + + it 'returns true for relative days' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('next Monday')).to eq(true) + expect(Msf::Exploit::Local::Timespec.valid_timespec?('last Friday')).to eq(true) # unlikely to ever be used for our context + end + + it 'returns true for mm/dd/yy based date' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('07/04/23')).to eq(true) + end + + it 'returns true for mmddyy based date' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('010124')).to eq(true) + end + + it 'returns true for dd.mm.yyyy based date' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('31.12.2023')).to eq(true) + end + end +end \ No newline at end of file diff --git a/spec/lib/msf/core/post/linux/user_spec.rb b/spec/lib/msf/core/post/linux/user_spec.rb new file mode 100644 index 0000000000000..3c95e165c93cb --- /dev/null +++ b/spec/lib/msf/core/post/linux/user_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +RSpec.describe Msf::Post::Linux::User do + subject do + mod = ::Msf::Module.new + mod.extend described_class + mod + end + + describe '#get_home_dir' do + let(:user) { 'testuser' } + let(:expected_command) { "grep '^#{user}:' /etc/passwd | cut -d ':' -f 6" } + + context 'when the user exists' do + it 'returns the home directory path from /etc/passwd' do + expect(subject).to receive(:cmd_exec) + .with(expected_command) + .and_return("/home/testuser\n") + + result = subject.get_home_dir(user) + expect(result).to eq('/home/testuser') + end + end + + context 'when the user does not exist in /etc/passwd' do + it 'returns an empty string' do + expect(subject).to receive(:cmd_exec) + .with(expected_command) + .and_return("\n") + + result = subject.get_home_dir(user) + expect(result).to eq('') + end + end + + end +end