Tinyauth: User enumeration attack by timing oracle
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
cve-program · CVE-2026-77582
PublishedTinyauth is an authentication and authorization server. Prior to 5.1.0, Tinyauth exposes a remotely observable timing difference between authentication attempts for existing and nonexistent local usernames. internal/controller/user_controller.go loginHandler and internal/middleware/context_middleware.go basicAuth return quickly after internal/service/auth_service.go reports a missing user, while an existing user causes bcrypt password verification work. Repeated measurements can therefore disclose valid usernames and support targeted credential attacks. This issue is fixed in version 5.1.0.
- Source published
- 21 Sep 2026 16:43:43 UTC
- Source modified
- 23 Sep 2026 19:16:39 UTC
- Observed here
- 23 Oct 2026 15:48:27 UTC
- Source terms
- CVE Program Terms of Use
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 4.0 · 6.9 CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
euvd · EUVD-2026-84014
PublishedTinyauth is an authentication and authorization server. Prior to 5.1.0, Tinyauth exposes a remotely observable timing difference between authentication attempts for existing and nonexistent local usernames. internal/controller/user_controller.go loginHandler and internal/middleware/context_middleware.go basicAuth return quickly after internal/service/auth_service.go reports a missing user, while an existing user causes bcrypt password verification work. Repeated measurements can therefore disclose valid usernames and support targeted credential attacks. This issue is fixed in version 5.1.0.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: Sep 21, 2026, 4:43:43 PM
dateUpdated: Sep 23, 2026, 7:16:39 PM
- Source published
- Sep 21, 2026, 4:43:43 PM (timezone not stated)
- Source modified
- Sep 23, 2026, 7:16:39 PM (timezone not stated)
- Observed here
- 11 Nov 2026 16:09:07 UTC
- Source terms
- ENISA EUVD · source terms and attribution apply
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.
EUVD CVSS 4.0 · 6.9 CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
nvd · CVE-2026-77582
PublishedTinyauth is an authentication and authorization server. Prior to 5.1.0, Tinyauth exposes a remotely observable timing difference between authentication attempts for existing and nonexistent local usernames. internal/controller/user_controller.go loginHandler and internal/middleware/context_middleware.go basicAuth return quickly after internal/service/auth_service.go reports a missing user, while an existing user causes bcrypt password verification work. Repeated measurements can therefore disclose valid usernames and support targeted credential attacks. This issue is fixed in version 5.1.0.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Deferred
Weakness classifications: CWE-208
Original publisher date fields
published: 2026-09-21T17:18:53.110
lastModified: 2026-09-30T17:32:07.107
- Source published
- 21 Sep 2026 17:18:53 UTC
- Source modified
- 30 Sep 2026 17:32:07 UTC
- Observed here
- 25 Oct 2026 16:19:04 UTC
- Source terms
- NIST NVD usage terms; underlying source attribution retained
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 4.0 · [email protected] · Secondary · 6.9 CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
osv · GHSA-456h-ww26-f758
Published### Summary It's possible to enumerate users through a timing oracle. In other words: I can easily check if a username exists or not by observing the timing differences between logins. ### PoC Setup a tinyauth server with a local user. It can be over the network. Try to log in with the local user, using an incorrect password: there is a noticeable delay. You know that the user exists. Now try to log in with a username that does not exist, using an incorrect password: it will complete almost immediately. You know that the user does not exist. ### Expected vs actual behavior Login attempts should take roughly the same amount of time when the user exists vs when the user does not exist. Right now, nonexistent user attempts are way faster, meaning if your login attempt is slow, then the username exists for sure. ### Details Existing usernames will take an extra ~50 milliseconds to respond to login attempts, while non-existing usernames will take only ~50 microseconds (1000x less time) to return an incorrect password response. Even taking network traffic into consideration, it is trivial to check whether a local user exists or not. === Existing user, wrong password === #1: 50.52ms #2: 46.24ms #3: 43.57ms === Nonexistent user === #1: 43.03µs #2: 48.45µs #3: 59.33µs ### Suggested fix This can be solved by checking a dummy password hash when a user is not found before returning a query, this will mimic the exact same delay irrelevant of hardware capabilities. Potential patch: ``` From ca102773e0303f6025480efb26db379e3570a350 Mon Sep 17 00:00:00 2001 From: Disyer <[email protected]> Date: Tue, 14 Jul 2026 12:35:07 +0300 Subject: [PATCH] fix: prevent user enumeration by means of timing attack --- internal/controller/user_controller.go | 1 + internal/middleware/context_middleware.go | 1 + internal/service/auth_service.go | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go index ae6c23b..2ecc996 100644 --- a/internal/controller/user_controller.go +++ b/internal/controller/user_controller.go @@ -90,6 +90,7 @@ func (controller *UserController) loginHandler(c *gin.Context) { if err != nil { if errors.Is(err, service.ErrUserNotFound) { + controller.auth.DummyCheckPassword(req.Password) controller.log.App.Warn().Str("username", req.Username).Msg("User not found during login attempt") controller.auth.RecordLoginAttempt(req.Username, false) controller.log.AuditLoginFailure(req.Username, "unknown", c.ClientIP(), "user not found") diff --git a/internal/middleware/context_middleware.go b/internal/middleware/context_middleware.go index f49c85a..f70c9e7 100644 --- a/internal/middleware/context_middleware.go +++ b/internal/middleware/context_middleware.go @@ -244,6 +244,7 @@ func (m *ContextMiddleware) basicAuth(username string, password string) (*model. search, err := m.auth.SearchUser(username) if err != nil { + m.auth.DummyCheckPassword(password) return nil, nil, fmt.Errorf("error searching for user: %w", err) } diff --git a/internal/service/auth_service.go b/internal/service/auth_service.go index eeb5c8e..9cbc105 100644 --- a/internal/service/auth_service.go +++ b/internal/service/auth_service.go @@ -28,6 +28,10 @@ import ( const MaxOAuthPendingSessions = 256 const OAuthCleanupCount = 16 +// a dummy hash to check when a user is not found, to prevent user enumeration +// hardcoded to prevent having to generate a hash on every startup +const dummyHash = "$2a$10$iFQ6./j2a.UShYQOzWh4vOlnoze5DmHI3tRY3WU4YMj3zwp.t3TnO" + var ( ErrUserNotFound = errors.New("user not found") ) @@ -204,6 +208,12 @@ func (auth *AuthService) CheckUserPassword(search model.UserSearch, password str return errors.New("user authentication failed") } +func (auth *AuthService) DummyCheckPassword(password string) { + // When a user is not found, we still want to perform a password check to prevent + // timing attacks that could reveal whether a user exists or not + bcrypt.CompareHashAndPassword([]byte(dummyHash), []byte(password)) +} + func (auth *AuthService) GetLocalUser(username string) *model.LocalUser { if auth.runtime.LocalUsers == nil { return nil -- 2.55.0 ``` After testing the proposed fix, there is no discernible timing difference: === Existing user, wrong password === #1: 43.23ms #2: 43.26ms #3: 42.57ms === Nonexistent user === #1: 42.45ms #2: 42.11ms #3: 47.71ms
- Source published
- 22 Sep 2026 20:37:06 UTC
- Source modified
- 1 Oct 2026 20:56:17 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V4 · CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
Publisher rating · MODERATE
osv · GO-2026-6555
PublishedTinyauth: User enumeration attack by timing oracle in github.com/tinyauthapp/tinyauth
- Source published
- 1 Oct 2026 20:23:38 UTC
- Source modified
- 1 Oct 2026 20:45:12 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- OSV · upstream source license applies
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · OSV · upstream source license applies. Source text is retained; field grouping and change labels are provided by ChangeIntel.