GitHub Advisoryadvisory · npm
GHSA-gf43-24g3-5hw2 Apostrophe has a Weak Password Recovery Mechanism for Forgotten Password and Improper Input Validation
HIGH
Official page ↗
- Aliases
- CVE-2026-45013
- Ecosystem
- npm
- Packages
- apostrophe
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
- Weaknesses
- CWE-20, CWE-640
- Reviewed
- 2026-05-14
Affected packages
• npm apostrophe: <= 4.29.0 (no fix yet)
Description
Summary
ApostropheCMS's password reset flow constructs the reset URL using `req.hostname`,
which is derived directly from the attacker-controlled HTTP `Host` header when
`apos.baseUrl` is not explicitly configured. An unauthenticated attacker who knows
a victim's email address can send a crafted reset request that causes the application
to email the victim a reset link pointing to the attacker's domain. When the victim
clicks the link, the valid reset token is delivered to the attacker, enabling full
account takeover.
Affected Component
`modules/@apostrophecms/login/index.js` — `resetRequest` route
Precondition: `passwordReset: true` is set and `apos.baseUrl` is not configured.
Vulnerability Details
The `setPrefixUrls` middleware (i18n layer) builds `req.baseUrl` using `req.hostname`:
```js
// Simplified from i18n middleware
req.baseUrl = `${req.protocol}://${req.hostname}`;
req.absoluteUrl = req.baseUrl + req.url;
```
The `resetRequest` handler then passes this tainted value directly into URL construction:
```js
const parsed = new URL(
req.absoluteUrl, // ← tainted by attacker's Host header
self.apos.baseUrl
? undefined
: `${req.protocol}://${req.hostname}${port}` // ← also tainted
);
parsed.pathname = '/login';
parsed.searchParams.append('reset', reset); // real, valid token
parsed.searchParams.append('email', user.email);
await self.email(..., { url: parsed.toString() }, ...);
// Email sent to victim with URL pointing to attacker-controlled domain
```
When `apos.baseUrl` is configured, it is used unconditionally and the attacker's
`Host` header is ignored — that path is not vulnerable.
Attack Scenario
1. Attacker identifies a valid user email (e.g. from the site's public interface).
2. Attacker sends:
```
POST /api/v1/login/reset-request
Host: evil.attacker.com
Content-Type: application/json
{"email": "[email protected]"}
```
3. The application emails the victim:
```
Click here to reset your password:
http://evil.attacker.com/login?reset=TOKEN&[email protected]
```
4. Victim clicks the link; attacker's server captures `TOKEN`.
5. Attacker calls the real target's reset endpoint with the captured token and
sets a new password — full account takeover.
Preconditions
• `passwordReset: true` configured in login module options (opt-in)
• `apos.baseUrl` is not set (common in development and some production deployments)
• Attacker knows or can enumerate a valid account email
Impact
Full account takeover of any account whose email address is known to the attacker.
No authentication or interaction beyond sending a single HTTP request is required
from the attacker. The victim need only click a link in a legitimate-looking
password reset email from their own site.
Remediation
Operators (immediate): Always set `apos.baseUrl` in your configuration:
```js
// app.js or module configuration
modules: {
'@apostrophecms/express': {
options: {
baseUrl: 'https://yourdomain.com'
}
}
}
```
Framework fix (recommended): The `resetRequest` route should refuse to proceed
if `apos.baseUrl` is not configured, rather than falling back to the tainted
`req.hostname`. Example:
```js
// In resetRequest handler
if (!self.apos.baseUrl) {
throw self.apos.error(
'invalid',
'apos.baseUrl must be configured to enable password reset'
);
}
const parsed = new URL(self.loginUrl(), self.apos.baseUrl);
```
This eliminates the attacker-controlled input entirely from the URL construction path.
References
• OWASP: Host Header Injection
• CWE-640: Weak Password Recovery Mechanism for Forgotten Password
Related
Alias · CVE (1)
- CVE-2026-45013 ApostropheCMS is an open-source Node.js content management system. vulnerability