CVE-2026-33877: ApostropheCMS: User Enumeration via Timing Side Channel in Password Reset Endpoint

Published Apr 15, 2026
·
Updated

Summary

The password reset endpoint (/api/v1/@apostrophecms/login/reset-request) exhibits a measurable timing side channel that allows unauthenticated attackers to enumerate valid usernames and email addresses. When a user is not found, the handler returns after a fixed 2-second artificial delay, but when a valid user is found, it performs database writes and SMTP operations with no equivalent delay normalization, producing a distinguishable timing profile.

Details

The resetRequest handler in modules/@apostrophecms/login/index.js attempts to obscure the user-not-found path with an artificial delay, but fails to normalize the timing of the user-found path:

User not found — fixed 2000ms delay (index.js:309-314): javascript if (!user) { await wait(); // wait = (t = 2000) => Promise.delay(t) self.apos.util.error( Reset password request error - the user ${email} doesn\t exist. ); return; }

User found — variable-duration DB + SMTP operations, no artificial delay (index.js:323-355): javascript const reset = self.apos.util.generateId(); user.passwordReset = reset; user.passwordResetAt = new Date(); await self.apos.user.update(req, user, { permissions: false }); // ... URL construction ... await self.email(req, 'passwordResetEmail', { user, url: parsed.toString(), site }, { to: user.email, subject: req.t('apostrophe:passwordResetRequest', { site }) });

The user-found path includes a MongoDB update() call and an SMTP email() send, which together produce response times that differ measurably from the fixed 2000ms delay. Depending on SMTP server latency, responses for valid users will either be noticeably faster (local/fast SMTP) or slower (remote SMTP) than the constant 2-second delay for invalid users.

Additionally, the getPasswordResetUser method (index.js:664-666) accepts both username and email via an $or query, enabling enumeration of both identifiers: javascript const criteriaOr = [ { username: email }, { email } ];

There is no rate limiting on the reset endpoint. The checkLoginAttempts throttle (index.js:978) is only applied to the login flow, allowing unlimited rapid probing of the reset endpoint.

PoC

Prerequisites: An Apostrophe instance with passwordReset: true enabled in @apostrophecms/login configuration.

Step 1 — Baseline invalid user timing: bash for i in $(seq 1 10); do curl -s -o /dev/null -w "%{timetotal}\n" \ -X POST http://localhost:3000/api/v1/@apostrophecms/login/reset-request \ -H "Content-Type: application/json" \ -d '{"email": "nonexistent-user-'$i'@example.com"}' done Expected: all responses cluster tightly around 2.0xx seconds

Step 2 — Test known valid user: bash for i in $(seq 1 10); do curl -s -o /dev/null -w "%{timetotal}\n" \ -X POST http://localhost:3000/api/v1/@apostrophecms/login/reset-request \ -H "Content-Type: application/json" \ -d '{"email": "admin"}' done Expected: response times differ from 2.0s baseline (faster with local SMTP, slower with remote SMTP)

Step 3 — Statistical comparison: The two distributions will show a measurable divergence. With a local mail server, valid-user responses typically complete in <500ms. With a remote SMTP server, valid-user responses may take 3-5+ seconds. Either way, the timing is distinguishable from the fixed 2000ms invalid-user delay.

Impact

- Account enumeration: An unauthenticated attacker can determine whether a given username or email address has an account in the Apostrophe instance. - Credential stuffing preparation: Confirmed valid accounts can be targeted with credential stuffing attacks using breached password databases. - Phishing targeting: Knowledge of valid accounts enables targeted phishing campaigns against confirmed users. - No rate limiting: The absence of throttling on the reset endpoint allows high-speed automated enumeration. - Mitigating factor: The passwordReset option defaults to false (index.js:62), so only instances that explicitly enable password reset are affected.

Recommended Fix

Normalize all code paths to a constant minimum duration, ensuring the response time does not leak whether a user was found:

javascript async resetRequest(req) { const MINRESPONSETIME = 2000; const startTime = Date.now(); const site = (req.headers.host || '').replace(/:\d+$/, ''); const email = self.apos.launder.string(req.body.email); if (!email.length) { throw self.apos.error('invalid', req.t('apostrophe:loginResetEmailRequired')); } let user; try { user = await self.getPasswordResetUser(req.body.email); } catch (e) { self.apos.util.error(e); } if (!user) { self.apos.util.error( Reset password request error - the user ${email} doesn\t exist. ); } else if (!user.email) { self.apos.util.error( Reset password request error - the user ${user.username} doesn\t have an email. ); } else { const reset = self.apos.util.generateId(); user.passwordReset = reset; user.passwordResetAt = new Date(); await self.apos.user.update(req, user, { permissions: false }); let port = (req.headers.host || '').split(':')[1]; if (!port || [ '80', '443' ].includes(port)) { port = ''; } else { port = :${port}; } const parsed = new URL( req.absoluteUrl, self.apos.baseUrl ? undefined : ${req.protocol}://${req.hostname}${port} ); parsed.pathname = self.login(); parsed.search = '?'; parsed.searchParams.append('reset', reset); parsed.searchParams.append('email', user.email); try { await self.email(req, 'passwordResetEmail', { user, url: parsed.toString(), site }, { to: user.email, subject: req.t('apostrophe:passwordResetRequest', { site }) }); } catch (err) { self.apos.util.error(Error while sending email to ${user.email}, err); } } // Pad all paths to a constant minimum duration const elapsed = Date.now() - startTime; if (elapsed < MINRESPONSETIME) { await Promise.delay(MINRESPONSETIME - elapsed); } },

Additionally, consider applying rate limiting to the reset-request endpoint to prevent high-speed enumeration attempts.

Other sources

ApostropheCMS is an open-source Node.js content management system. Versions 4.28.0 and prior contain a timing side-channel vulnerability in the password reset endpoint (/api/v1/@apostrophecms/login/reset-request) that allows unauthenticated username and email enumeration. When a user is not found, the handler returns after a fixed 2-second artificial delay, but when a valid user is found, it performs a MongoDB update and SMTP email send with no equivalent delay normalization, producing measurably different response times. The endpoint also accepts both username and email via an $or query, and has no rate limiting as the existing checkLoginAttempts throttle only applies to the login flow. This enables automated enumeration of valid accounts for use in credential stuffing or targeted phishing. Only instances that have explicitly enabled the passwordReset option are affected, as it defaults to false. This issue has been fixed in version 4.29.0.

NVD

Affected Software

3 affected componentsFixes available
npm/apostrophe<=4.28.0
npm/apostrophe<4.29.0
4.29.0
apostrophecms ApostropheCMS<4.29.0

Event History

Apr 15, 2026
CVE Published
via MITRE·07:11 PM
Data Sourced
via MITRE·07:11 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·08:16 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·08:16 PM
RemedyAffected Software
Apr 16, 2026
Advisory Published
via GitHub·08:42 PM
Data Sourced
via GitHub·08:42 PM
DescriptionSeverityWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-33877?

CVE-2026-33877 has been classified as a medium severity vulnerability.

2

How do I fix CVE-2026-33877?

To fix CVE-2026-33877, upgrade to ApostropheCMS version 4.28.1 or later.

3

What types of attacks can CVE-2026-33877 facilitate?

CVE-2026-33877 can facilitate user enumeration attacks through timing side-channel vulnerabilities.

4

Which versions of ApostropheCMS are affected by CVE-2026-33877?

ApostropheCMS versions up to and including 4.28.0 are affected by CVE-2026-33877.

5

What component of ApostropheCMS is vulnerable in CVE-2026-33877?

The vulnerability in CVE-2026-33877 exists in the password reset endpoint of ApostropheCMS.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203