Ivanti Connect Secure - Authenticated RCE via OpenSSL CRLF Injection (CVE-2024-37404)

Summary

Ivanti Connect Secure versions prior to 22.7R2.1 and 22.7R2.2, and Ivanti Policy Secure versions prior to 22.7R1.1, contain a CRLF injection vulnerability which could be exploited by an authenticated administrator to execute arbitrary code with root privileges.

Description

An attacker with administrative access to the web application, potentially gained through exploitation of previous vulnerabilities or credential compromise, could execute arbitrary code on the underlying system with root privileges.

Impact

This vulnerability allows an authenticated administrator to execute arbitrary code with root privileges on the underlying system.

While the vulnerability requires authentication, past vulnerabilities, such as CVE-2020-8260 and CVE-2020-8243, which also required authentication, were exploited in the wild. However, Ivanti have advised that they are not aware of exploitation of this vulnerability in the wild prior to public disclosure.

Mitigation Steps

Update to ICS version 22.7R2.1, 22.7R2.2, or Ivanti Policy Secure 22.7R1.1.

Follow the mitigation guidance published by Ivanti to ensure administrative interfaces are not exposed to the interface, thus limiting the opportunity for attackers to exploit vulnerabilities such as the one described in this advisory.

Affected Versions

ICS versions prior to 22.7R2.1.

Timeline

  • 2024-04-05: Vulnerability reported to Ivanti.
  • 2024-04-08: Acknowledgement received from Ivanti.
  • 2024-07-04: 90-day disclosure deadline passed.
  • 2024-07-09: Follow-up sent to Ivanti requesting status update after the deadline.
  • 2024-07-10: Ivanti confirmed reproduction of the issue and indicated that a patch had been developed, with an expected release timeline of late July 2024. CVE-2024-37404 reserved.
  • 2024-09-03: Follow-up sent to Ivanti requesting current status.
  • 2024-09-04: Ivanti advised that they plan to provide an update by Friday, September 6, 2024.
  • 2024-09-10: Follow-up sent to Ivanti advising of our intention to publish by Monday, September 16, 2024.
  • 2024-09-11: Ivanti replied stating that the fix was actually included in ICS 22.7R2.1.
  • 2024-10-04: Coordinated disclosure date of 2024-10-08 agreed with Ivanti.
  • 2024-10-08: Ivanti advisory published.
  • 2024-10-08: AmberWolf advisory published.

References

Vulnerability Details

CSR Generation

Ivanti Connect Secure provides functionality for administrative users to generate new certificates via the admin web application at the URL: /dana-admin/cert/admincert.cgi

The following screenshot shows the admincert.cgi page:

Admin Certificate Generation Page

Admin Certificate Generation Page

When clicking on the “New CSR” option, the user is then prompted to fill out some details which will be used to generate a CSR, via the CGI script at /dana-admin/cert/admincertnewcsr.cgi

New Certificate Signing Request Form

New Certificate Signing Request Form

The POST request that this form generates looks like the below:

POST /dana-admin/cert/admincertnewcsr.cgi HTTP/1.1
Host: 192.168.2.92
Cookie: DSSignInURL=/admin; SUPPORTCHROMEOS=1; PHC_DISABLED=1; DSBrowserID=f5afd4b930af818747c03bc6f2adaf59; id=state_16f05e4e8ed9a95921f9e5561b63b977; DSSIGNIN=url_admin; DSPERSISTMSG=; DSDID=00064f3b9acd709a; DSFirstAccess=1705398262; DSLastAccess=1705398282; DSLaunchURL=2F64616E612D61646D696E2F636572742F61646D696E636572742E636769; DSID=4b2c0f96862dc17c23622ef26a7a6db8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 594
Origin: https://192.168.2.92
Referer: https://192.168.2.92/dana-admin/cert/admincertnewcsr.cgideletecsr=&selectedCSRIds=&xsauth=4e2fea1b8e12585a4744fc72820de0cc
Connection: close

xsauth=4e2fea1b8e12585a4744fc72820de0cc&commonName=vpn.pcs.local&organizationName=Example+Corp&organizationalUnitName=IT+Group&localityName=SomeCity&stateOrProvinceName=California&countryName=US&emailAddress=test%40example.com&keytype=RSA&keylength=1024&eccurve=prime256v1&random=aaaaaaaaaaaaaaaaaaaa&newcsr=yes&certType=device&btnCreateCSR=Create+CSR

Reviewing the source code for the admincertnewcsr.cgi, which is written in Perl, we can see that main subroutine takes these values from the POST request.

The commonName is validated against a regex, but the other values appear to have no validation checks:

Common Name Validation

Common Name Validation

These user-controlled values are added to a new object from: DSCert::NewCSR(). This is a Perl module which can be found at /home/perl/DSCert.pm. This module contains Swig wrappers, which call into native code in DSCert.so:

DSCert::NewCSR

DSCert::NewCSR

DSCert.so imports the DSCert::NewCSR::NewCSR function from libdsplibs.so:

NewCSR Function

NewCSR Function

Once a CSR object has been created in admincertnewcsr.cgi, it then calls DSCert::Admin::addCSR

addCSR Function

addCSR Function

Since we know this is exported by libdsplibs.so, we can go and decompile the code in IDA to see how the CSR is generated.

There is some additional validation in this function for certain parameters, such as the country code:

Validation of the Country Code

Validation of the Country Code

However, a number of parameters are passed unfiltered, and written into a CSR config file at /home/runtime/tmp/csrconf.<pid>

CSR Config File

CSR Config File

This CSR config is then passed to DSCert::runOpenssl, which executes the command:

openssl req config /home/runtime/tmp/csrconf.<pid> -new -utf8 -out /home/runtime/tmp/csr.<pid>
DSCert::runOpenssl

DSCert::runOpenssl

As documented in the OpenSSL docs, there are various fields which can be passed within the configuration. However, of interest to an attacker is the engine option, which allows the user to specify an arbitrary “engine” to be used. As described here, the Engine API provides an interface for “adding alternative implementations of cryptographic primitives”, and has since been superseded in OpenSSL 3.0 by the Provider API.

From an attacker perspective, this feature essentially allows us to provide a path to an external library where OpenSSL will attempt to load the specified engine from. If an attacker can inject this option into a config file, then they can gain arbitrary code execution when the engine is loaded.

CRLF Injection

As mentioned previously, there is only limited validation carried out on the user-input before it is passed into the config file. Therefore, an attacker could inject CRLF characters in one of the POST parameters to add their own section into the config file, specifying an arbitrary engine path.

To confirm that CRLF injection is possible we can do a simple test. First we make a request, injecting into the localityName parameter, with a CRLF sequence after the value, followed by the value [foo]. If this is successful, and the certificate doesn’t contain [foo] in the locality, then it’s a good sign that CRLF injection is working and the [foo] value is ignored, since it’s interpreted as an empty section:

CRLF Injection Test

CRLF Injection Test

As suspected, this is successful, and the locality shows that the [foo] value was ignored.

Locality Value Shown in CSR Details

Locality Value Shown in CSR Details

Next, we can attempt to inject a new engine section, and should expect an error to be raised, as we will pass a path to a non-existent engine library.

As shown below, this results in an error, since the /tmp/test.so file doesn’t exist on disk:

CSR Generation Failure

CSR Generation Failure

This confirms that the server is vulnerable to CRLF injection.

Proof of Concept

To confirm whether this issue can be exploited to gain Remote Code Execution, we need to create an OpenSSL “engine” payload (which is simply a shared object file).

The Ivanti Connect Secure appliance is based on Centos 6.4, which is using a fairly old kernel and glibc version. We compiled our test payload using the ubuntu:xenial x86 Docker image.

Looking again at our PoC request, we will inject a CRLF sequence with a fake engine path like the following:

[default]
openssl_conf = openssl_init
[openssl_init]
engines = engine_section
[engine_section]
foo = foo_section
[foo_section]
engine_id = foo
dynamic_path = /tmp/test.so
init = 0

However, there is one issue we need to overcome first. The dynamic_path option must reference a shared object file on disk. This means we need to find a way to get a file onto the target server so that it can be referenced in the OpenSSL config.

Interestingly, OpenSSL does not appear to mind:

  • What permissions are assigned to the file referenced under dynamic_path. It does not need to be executable.
  • The extension of the file. It does not need to end with .so.

The Ivanti Connect Secure appliance includes a feature called “Client Log Upload”, which allows users of the VPN client and/or Java Applet to upload client logs for diagnostic purposes.

When a user uploads client logs, these are stored in the folder /home/runtime/uploadlog/, with a filename containing the timestamp and ending in .zip, for example: log-20240115-035029.zip. These files are uploaded via the /dana/uploadlog/uploadlog.cgi CGI script.

This CGI does not verify whether the client logs content is valid, nor whether the file itself is even a ZIP file.

ZIP File Processing

ZIP File Processing

The following screenshot shows the POST request used to upload log files:

Upload Logs POST Request

Upload Logs POST Request

The resulting .zip file name is then shown in the response:

ZIP File Uploaded Successfully

ZIP File Uploaded Successfully

Therefore, to get our payload onto the server at a known local path, we can simply upload a fake client log via uploadlog.cgi. We can then look up the file name and infer the path. This can then be referenced in our injected config, for example:

..
[foo_section]
engine_id = foo
dynamic_path = /home/runtime/uploadlog/log-20240115-035029.zip
init = 0

Note: client logs must first be enabled, and a low-privileged user account must be used to upload them. However, assuming the attacker has administrative access to the web application, both of these prerequisites can be satisfied directly by the attacker (i.e. by enabling client-log upload and/or adding a Local low-privileged user if required). Client logs can be enabled in: Users -> User Roles -> Users (Role) -> General -> Session Options -> Enable Upload

Now we can make a POST request containing our CRLF and OpenSSL payload. As shown in the below screenshot, this results in successful exploitation, and a reverse shell running under the root user is established:

Reverse Shell - Running as root User

Reverse Shell - Running as root User

You May Also Like